|  | /* | 
|  | * This file is subject to the terms and conditions of the GNU General Public | 
|  | * License.  See the file "COPYING" in the main directory of this archive | 
|  | * for more details. | 
|  | * | 
|  | * Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr> | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/mutex.h> | 
|  | #include <linux/err.h> | 
|  | #include <linux/clk.h> | 
|  | #include <linux/delay.h> | 
|  | #include <bcm63xx_cpu.h> | 
|  | #include <bcm63xx_io.h> | 
|  | #include <bcm63xx_regs.h> | 
|  | #include <bcm63xx_clk.h> | 
|  |  | 
|  | static DEFINE_MUTEX(clocks_mutex); | 
|  |  | 
|  |  | 
|  | static void clk_enable_unlocked(struct clk *clk) | 
|  | { | 
|  | if (clk->set && (clk->usage++) == 0) | 
|  | clk->set(clk, 1); | 
|  | } | 
|  |  | 
|  | static void clk_disable_unlocked(struct clk *clk) | 
|  | { | 
|  | if (clk->set && (--clk->usage) == 0) | 
|  | clk->set(clk, 0); | 
|  | } | 
|  |  | 
|  | static void bcm_hwclock_set(u32 mask, int enable) | 
|  | { | 
|  | u32 reg; | 
|  |  | 
|  | reg = bcm_perf_readl(PERF_CKCTL_REG); | 
|  | if (enable) | 
|  | reg |= mask; | 
|  | else | 
|  | reg &= ~mask; | 
|  | bcm_perf_writel(reg, PERF_CKCTL_REG); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Ethernet MAC "misc" clock: dma clocks and main clock on 6348 | 
|  | */ | 
|  | static void enet_misc_set(struct clk *clk, int enable) | 
|  | { | 
|  | u32 mask; | 
|  |  | 
|  | if (BCMCPU_IS_6338()) | 
|  | mask = CKCTL_6338_ENET_EN; | 
|  | else if (BCMCPU_IS_6345()) | 
|  | mask = CKCTL_6345_ENET_EN; | 
|  | else if (BCMCPU_IS_6348()) | 
|  | mask = CKCTL_6348_ENET_EN; | 
|  | else | 
|  | /* BCMCPU_IS_6358 */ | 
|  | mask = CKCTL_6358_EMUSB_EN; | 
|  | bcm_hwclock_set(mask, enable); | 
|  | } | 
|  |  | 
|  | static struct clk clk_enet_misc = { | 
|  | .set	= enet_misc_set, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * Ethernet MAC clocks: only revelant on 6358, silently enable misc | 
|  | * clocks | 
|  | */ | 
|  | static void enetx_set(struct clk *clk, int enable) | 
|  | { | 
|  | if (enable) | 
|  | clk_enable_unlocked(&clk_enet_misc); | 
|  | else | 
|  | clk_disable_unlocked(&clk_enet_misc); | 
|  |  | 
|  | if (BCMCPU_IS_6358()) { | 
|  | u32 mask; | 
|  |  | 
|  | if (clk->id == 0) | 
|  | mask = CKCTL_6358_ENET0_EN; | 
|  | else | 
|  | mask = CKCTL_6358_ENET1_EN; | 
|  | bcm_hwclock_set(mask, enable); | 
|  | } | 
|  | } | 
|  |  | 
|  | static struct clk clk_enet0 = { | 
|  | .id	= 0, | 
|  | .set	= enetx_set, | 
|  | }; | 
|  |  | 
|  | static struct clk clk_enet1 = { | 
|  | .id	= 1, | 
|  | .set	= enetx_set, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * Ethernet PHY clock | 
|  | */ | 
|  | static void ephy_set(struct clk *clk, int enable) | 
|  | { | 
|  | if (!BCMCPU_IS_6358()) | 
|  | return; | 
|  | bcm_hwclock_set(CKCTL_6358_EPHY_EN, enable); | 
|  | } | 
|  |  | 
|  |  | 
|  | static struct clk clk_ephy = { | 
|  | .set	= ephy_set, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * Ethernet switch clock | 
|  | */ | 
|  | static void enetsw_set(struct clk *clk, int enable) | 
|  | { | 
|  | if (!BCMCPU_IS_6368()) | 
|  | return; | 
|  | bcm_hwclock_set(CKCTL_6368_ROBOSW_CLK_EN | | 
|  | CKCTL_6368_SWPKT_USB_EN | | 
|  | CKCTL_6368_SWPKT_SAR_EN, enable); | 
|  | if (enable) { | 
|  | u32 val; | 
|  |  | 
|  | /* reset switch core afer clock change */ | 
|  | val = bcm_perf_readl(PERF_SOFTRESET_6368_REG); | 
|  | val &= ~SOFTRESET_6368_ENETSW_MASK; | 
|  | bcm_perf_writel(val, PERF_SOFTRESET_6368_REG); | 
|  | msleep(10); | 
|  | val |= SOFTRESET_6368_ENETSW_MASK; | 
|  | bcm_perf_writel(val, PERF_SOFTRESET_6368_REG); | 
|  | msleep(10); | 
|  | } | 
|  | } | 
|  |  | 
|  | static struct clk clk_enetsw = { | 
|  | .set	= enetsw_set, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * PCM clock | 
|  | */ | 
|  | static void pcm_set(struct clk *clk, int enable) | 
|  | { | 
|  | if (!BCMCPU_IS_6358()) | 
|  | return; | 
|  | bcm_hwclock_set(CKCTL_6358_PCM_EN, enable); | 
|  | } | 
|  |  | 
|  | static struct clk clk_pcm = { | 
|  | .set	= pcm_set, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * USB host clock | 
|  | */ | 
|  | static void usbh_set(struct clk *clk, int enable) | 
|  | { | 
|  | if (BCMCPU_IS_6348()) | 
|  | bcm_hwclock_set(CKCTL_6348_USBH_EN, enable); | 
|  | else if (BCMCPU_IS_6368()) | 
|  | bcm_hwclock_set(CKCTL_6368_USBH_CLK_EN, enable); | 
|  | } | 
|  |  | 
|  | static struct clk clk_usbh = { | 
|  | .set	= usbh_set, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * SPI clock | 
|  | */ | 
|  | static void spi_set(struct clk *clk, int enable) | 
|  | { | 
|  | u32 mask; | 
|  |  | 
|  | if (BCMCPU_IS_6338()) | 
|  | mask = CKCTL_6338_SPI_EN; | 
|  | else if (BCMCPU_IS_6348()) | 
|  | mask = CKCTL_6348_SPI_EN; | 
|  | else | 
|  | /* BCMCPU_IS_6358 */ | 
|  | mask = CKCTL_6358_SPI_EN; | 
|  | bcm_hwclock_set(mask, enable); | 
|  | } | 
|  |  | 
|  | static struct clk clk_spi = { | 
|  | .set	= spi_set, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * XTM clock | 
|  | */ | 
|  | static void xtm_set(struct clk *clk, int enable) | 
|  | { | 
|  | if (!BCMCPU_IS_6368()) | 
|  | return; | 
|  |  | 
|  | bcm_hwclock_set(CKCTL_6368_SAR_CLK_EN | | 
|  | CKCTL_6368_SWPKT_SAR_EN, enable); | 
|  |  | 
|  | if (enable) { | 
|  | u32 val; | 
|  |  | 
|  | /* reset sar core afer clock change */ | 
|  | val = bcm_perf_readl(PERF_SOFTRESET_6368_REG); | 
|  | val &= ~SOFTRESET_6368_SAR_MASK; | 
|  | bcm_perf_writel(val, PERF_SOFTRESET_6368_REG); | 
|  | mdelay(1); | 
|  | val |= SOFTRESET_6368_SAR_MASK; | 
|  | bcm_perf_writel(val, PERF_SOFTRESET_6368_REG); | 
|  | mdelay(1); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static struct clk clk_xtm = { | 
|  | .set	= xtm_set, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * Internal peripheral clock | 
|  | */ | 
|  | static struct clk clk_periph = { | 
|  | .rate	= (50 * 1000 * 1000), | 
|  | }; | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Linux clock API implementation | 
|  | */ | 
|  | int clk_enable(struct clk *clk) | 
|  | { | 
|  | mutex_lock(&clocks_mutex); | 
|  | clk_enable_unlocked(clk); | 
|  | mutex_unlock(&clocks_mutex); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL(clk_enable); | 
|  |  | 
|  | void clk_disable(struct clk *clk) | 
|  | { | 
|  | mutex_lock(&clocks_mutex); | 
|  | clk_disable_unlocked(clk); | 
|  | mutex_unlock(&clocks_mutex); | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL(clk_disable); | 
|  |  | 
|  | unsigned long clk_get_rate(struct clk *clk) | 
|  | { | 
|  | return clk->rate; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL(clk_get_rate); | 
|  |  | 
|  | struct clk *clk_get(struct device *dev, const char *id) | 
|  | { | 
|  | if (!strcmp(id, "enet0")) | 
|  | return &clk_enet0; | 
|  | if (!strcmp(id, "enet1")) | 
|  | return &clk_enet1; | 
|  | if (!strcmp(id, "enetsw")) | 
|  | return &clk_enetsw; | 
|  | if (!strcmp(id, "ephy")) | 
|  | return &clk_ephy; | 
|  | if (!strcmp(id, "usbh")) | 
|  | return &clk_usbh; | 
|  | if (!strcmp(id, "spi")) | 
|  | return &clk_spi; | 
|  | if (!strcmp(id, "xtm")) | 
|  | return &clk_xtm; | 
|  | if (!strcmp(id, "periph")) | 
|  | return &clk_periph; | 
|  | if (BCMCPU_IS_6358() && !strcmp(id, "pcm")) | 
|  | return &clk_pcm; | 
|  | return ERR_PTR(-ENOENT); | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL(clk_get); | 
|  |  | 
|  | void clk_put(struct clk *clk) | 
|  | { | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL(clk_put); |