| /* |
| * clk.c |
| * |
| * DSP-BIOS Bridge driver support functions for TI OMAP processors. |
| * |
| * Clock and Timer services. |
| * |
| * Copyright (C) 2005-2006 Texas Instruments, Inc. |
| * |
| * This package is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR |
| * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED |
| * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. |
| */ |
| |
| #include <linux/types.h> |
| |
| /* ----------------------------------- Host OS */ |
| #include <dspbridge/host_os.h> |
| #include <plat/dmtimer.h> |
| #include <plat/mcbsp.h> |
| |
| /* ----------------------------------- DSP/BIOS Bridge */ |
| #include <dspbridge/dbdefs.h> |
| #include <dspbridge/cfg.h> |
| #include <dspbridge/drv.h> |
| #include <dspbridge/dev.h> |
| #include "_tiomap.h" |
| |
| /* ----------------------------------- Trace & Debug */ |
| #include <dspbridge/dbc.h> |
| |
| /* ----------------------------------- This */ |
| #include <dspbridge/clk.h> |
| |
| /* ----------------------------------- Defines, Data Structures, Typedefs */ |
| |
| #define OMAP_SSI_OFFSET 0x58000 |
| #define OMAP_SSI_SIZE 0x1000 |
| #define OMAP_SSI_SYSCONFIG_OFFSET 0x10 |
| |
| #define SSI_AUTOIDLE (1 << 0) |
| #define SSI_SIDLE_SMARTIDLE (2 << 3) |
| #define SSI_MIDLE_NOIDLE (1 << 12) |
| |
| /* Clk types requested by the dsp */ |
| #define IVA2_CLK 0 |
| #define GPT_CLK 1 |
| #define WDT_CLK 2 |
| #define MCBSP_CLK 3 |
| #define SSI_CLK 4 |
| |
| /* Bridge GPT id (1 - 4), DM Timer id (5 - 8) */ |
| #define DMT_ID(id) ((id) + 4) |
| |
| /* Bridge MCBSP id (6 - 10), OMAP Mcbsp id (0 - 4) */ |
| #define MCBSP_ID(id) ((id) - 6) |
| |
| static struct omap_dm_timer *timer[4]; |
| |
| struct clk *iva2_clk; |
| |
| struct dsp_ssi { |
| struct clk *sst_fck; |
| struct clk *ssr_fck; |
| struct clk *ick; |
| }; |
| |
| static struct dsp_ssi ssi; |
| |
| static u32 dsp_clocks; |
| |
| static inline u32 is_dsp_clk_active(u32 clk, u8 id) |
| { |
| return clk & (1 << id); |
| } |
| |
| static inline void set_dsp_clk_active(u32 *clk, u8 id) |
| { |
| *clk |= (1 << id); |
| } |
| |
| static inline void set_dsp_clk_inactive(u32 *clk, u8 id) |
| { |
| *clk &= ~(1 << id); |
| } |
| |
| static s8 get_clk_type(u8 id) |
| { |
| s8 type; |
| |
| if (id == DSP_CLK_IVA2) |
| type = IVA2_CLK; |
| else if (id <= DSP_CLK_GPT8) |
| type = GPT_CLK; |
| else if (id == DSP_CLK_WDT3) |
| type = WDT_CLK; |
| else if (id <= DSP_CLK_MCBSP5) |
| type = MCBSP_CLK; |
| else if (id == DSP_CLK_SSI) |
| type = SSI_CLK; |
| else |
| type = -1; |
| |
| return type; |
| } |
| |
| /* |
| * ======== dsp_clk_exit ======== |
| * Purpose: |
| * Cleanup CLK module. |
| */ |
| void dsp_clk_exit(void) |
| { |
| dsp_clock_disable_all(dsp_clocks); |
| |
| clk_put(iva2_clk); |
| clk_put(ssi.sst_fck); |
| clk_put(ssi.ssr_fck); |
| clk_put(ssi.ick); |
| } |
| |
| /* |
| * ======== dsp_clk_init ======== |
| * Purpose: |
| * Initialize CLK module. |
| */ |
| void dsp_clk_init(void) |
| { |
| static struct platform_device dspbridge_device; |
| |
| dspbridge_device.dev.bus = &platform_bus_type; |
| |
| iva2_clk = clk_get(&dspbridge_device.dev, "iva2_ck"); |
| if (IS_ERR(iva2_clk)) |
| dev_err(bridge, "failed to get iva2 clock %p\n", iva2_clk); |
| |
| ssi.sst_fck = clk_get(&dspbridge_device.dev, "ssi_sst_fck"); |
| ssi.ssr_fck = clk_get(&dspbridge_device.dev, "ssi_ssr_fck"); |
| ssi.ick = clk_get(&dspbridge_device.dev, "ssi_ick"); |
| |
| if (IS_ERR(ssi.sst_fck) || IS_ERR(ssi.ssr_fck) || IS_ERR(ssi.ick)) |
| dev_err(bridge, "failed to get ssi: sst %p, ssr %p, ick %p\n", |
| ssi.sst_fck, ssi.ssr_fck, ssi.ick); |
| } |
| |
| #ifdef CONFIG_OMAP_MCBSP |
| static void mcbsp_clk_prepare(bool flag, u8 id) |
| { |
| struct cfg_hostres *resources; |
| struct dev_object *hdev_object = NULL; |
| struct bridge_dev_context *bridge_context = NULL; |
| u32 val; |
| |
| hdev_object = (struct dev_object *)drv_get_first_dev_object(); |
| if (!hdev_object) |
| return; |
| |
| dev_get_bridge_context(hdev_object, &bridge_context); |
| if (!bridge_context) |
| return; |
| |
| resources = bridge_context->resources; |
| if (!resources) |
| return; |
| |
| if (flag) { |
| if (id == DSP_CLK_MCBSP1) { |
| /* set MCBSP1_CLKS, on McBSP1 ON */ |
| val = __raw_readl(resources->dw_sys_ctrl_base + 0x274); |
| val |= 1 << 2; |
| __raw_writel(val, resources->dw_sys_ctrl_base + 0x274); |
| } else if (id == DSP_CLK_MCBSP2) { |
| /* set MCBSP2_CLKS, on McBSP2 ON */ |
| val = __raw_readl(resources->dw_sys_ctrl_base + 0x274); |
| val |= 1 << 6; |
| __raw_writel(val, resources->dw_sys_ctrl_base + 0x274); |
| } |
| } else { |
| if (id == DSP_CLK_MCBSP1) { |
| /* clear MCBSP1_CLKS, on McBSP1 OFF */ |
| val = __raw_readl(resources->dw_sys_ctrl_base + 0x274); |
| val &= ~(1 << 2); |
| __raw_writel(val, resources->dw_sys_ctrl_base + 0x274); |
| } else if (id == DSP_CLK_MCBSP2) { |
| /* clear MCBSP2_CLKS, on McBSP2 OFF */ |
| val = __raw_readl(resources->dw_sys_ctrl_base + 0x274); |
| val &= ~(1 << 6); |
| __raw_writel(val, resources->dw_sys_ctrl_base + 0x274); |
| } |
| } |
| } |
| #endif |
| |
| /** |
| * dsp_gpt_wait_overflow - set gpt overflow and wait for fixed timeout |
| * @clk_id: GP Timer clock id. |
| * @load: Overflow value. |
| * |
| * Sets an overflow interrupt for the desired GPT waiting for a timeout |
| * of 5 msecs for the interrupt to occur. |
| */ |
| void dsp_gpt_wait_overflow(short int clk_id, unsigned int load) |
| { |
| struct omap_dm_timer *gpt = timer[clk_id - 1]; |
| unsigned long timeout; |
| |
| if (!gpt) |
| return; |
| |
| /* Enable overflow interrupt */ |
| omap_dm_timer_set_int_enable(gpt, OMAP_TIMER_INT_OVERFLOW); |
| |
| /* |
| * Set counter value to overflow counter after |
| * one tick and start timer. |
| */ |
| omap_dm_timer_set_load_start(gpt, 0, load); |
| |
| /* Wait 80us for timer to overflow */ |
| udelay(80); |
| |
| timeout = msecs_to_jiffies(5); |
| /* Check interrupt status and wait for interrupt */ |
| while (!(omap_dm_timer_read_status(gpt) & OMAP_TIMER_INT_OVERFLOW)) { |
| if (time_is_after_jiffies(timeout)) { |
| pr_err("%s: GPTimer interrupt failed\n", __func__); |
| break; |
| } |
| } |
| } |
| |
| /* |
| * ======== dsp_clk_enable ======== |
| * Purpose: |
| * Enable Clock . |
| * |
| */ |
| int dsp_clk_enable(IN enum dsp_clk_id clk_id) |
| { |
| int status = 0; |
| |
| if (is_dsp_clk_active(dsp_clocks, clk_id)) { |
| dev_err(bridge, "WARN: clock id %d already enabled\n", clk_id); |
| goto out; |
| } |
| |
| switch (get_clk_type(clk_id)) { |
| case IVA2_CLK: |
| clk_enable(iva2_clk); |
| break; |
| case GPT_CLK: |
| timer[clk_id - 1] = |
| omap_dm_timer_request_specific(DMT_ID(clk_id)); |
| break; |
| #ifdef CONFIG_OMAP_MCBSP |
| case MCBSP_CLK: |
| mcbsp_clk_prepare(true, clk_id); |
| omap_mcbsp_set_io_type(MCBSP_ID(clk_id), OMAP_MCBSP_POLL_IO); |
| omap_mcbsp_request(MCBSP_ID(clk_id)); |
| break; |
| #endif |
| case WDT_CLK: |
| dev_err(bridge, "ERROR: DSP requested to enable WDT3 clk\n"); |
| break; |
| case SSI_CLK: |
| clk_enable(ssi.sst_fck); |
| clk_enable(ssi.ssr_fck); |
| clk_enable(ssi.ick); |
| |
| /* |
| * The SSI module need to configured not to have the Forced |
| * idle for master interface. If it is set to forced idle, |
| * the SSI module is transitioning to standby thereby causing |
| * the client in the DSP hang waiting for the SSI module to |
| * be active after enabling the clocks |
| */ |
| ssi_clk_prepare(true); |
| break; |
| default: |
| dev_err(bridge, "Invalid clock id for enable\n"); |
| status = -EPERM; |
| } |
| |
| if (DSP_SUCCEEDED(status)) |
| set_dsp_clk_active(&dsp_clocks, clk_id); |
| |
| out: |
| return status; |
| } |
| |
| /** |
| * dsp_clock_enable_all - Enable clocks used by the DSP |
| * @dev_context Driver's device context strucure |
| * |
| * This function enables all the peripheral clocks that were requested by DSP. |
| */ |
| u32 dsp_clock_enable_all(u32 dsp_per_clocks) |
| { |
| u32 clk_id; |
| u32 status = -EPERM; |
| |
| for (clk_id = 0; clk_id < DSP_CLK_NOT_DEFINED; clk_id++) { |
| if (is_dsp_clk_active(dsp_per_clocks, clk_id)) |
| status = dsp_clk_enable(clk_id); |
| } |
| |
| return status; |
| } |
| |
| /* |
| * ======== dsp_clk_disable ======== |
| * Purpose: |
| * Disable the clock. |
| * |
| */ |
| int dsp_clk_disable(IN enum dsp_clk_id clk_id) |
| { |
| int status = 0; |
| |
| if (!is_dsp_clk_active(dsp_clocks, clk_id)) { |
| dev_err(bridge, "ERR: clock id %d already disabled\n", clk_id); |
| goto out; |
| } |
| |
| switch (get_clk_type(clk_id)) { |
| case IVA2_CLK: |
| clk_disable(iva2_clk); |
| break; |
| case GPT_CLK: |
| omap_dm_timer_free(timer[clk_id - 1]); |
| break; |
| #ifdef CONFIG_OMAP_MCBSP |
| case MCBSP_CLK: |
| mcbsp_clk_prepare(false, clk_id); |
| omap_mcbsp_free(MCBSP_ID(clk_id)); |
| break; |
| #endif |
| case WDT_CLK: |
| dev_err(bridge, "ERROR: DSP requested to disable WDT3 clk\n"); |
| break; |
| case SSI_CLK: |
| ssi_clk_prepare(false); |
| ssi_clk_prepare(false); |
| clk_disable(ssi.sst_fck); |
| clk_disable(ssi.ssr_fck); |
| clk_disable(ssi.ick); |
| break; |
| default: |
| dev_err(bridge, "Invalid clock id for disable\n"); |
| status = -EPERM; |
| } |
| |
| if (DSP_SUCCEEDED(status)) |
| set_dsp_clk_inactive(&dsp_clocks, clk_id); |
| |
| out: |
| return status; |
| } |
| |
| /** |
| * dsp_clock_disable_all - Disable all active clocks |
| * @dev_context Driver's device context structure |
| * |
| * This function disables all the peripheral clocks that were enabled by DSP. |
| * It is meant to be called only when DSP is entering hibernation or when DSP |
| * is in error state. |
| */ |
| u32 dsp_clock_disable_all(u32 dsp_per_clocks) |
| { |
| u32 clk_id; |
| u32 status = -EPERM; |
| |
| for (clk_id = 0; clk_id < DSP_CLK_NOT_DEFINED; clk_id++) { |
| if (is_dsp_clk_active(dsp_per_clocks, clk_id)) |
| status = dsp_clk_disable(clk_id); |
| } |
| |
| return status; |
| } |
| |
| u32 dsp_clk_get_iva2_rate(void) |
| { |
| u32 clk_speed_khz; |
| |
| clk_speed_khz = clk_get_rate(iva2_clk); |
| clk_speed_khz /= 1000; |
| dev_dbg(bridge, "%s: clk speed Khz = %d\n", __func__, clk_speed_khz); |
| |
| return clk_speed_khz; |
| } |
| |
| void ssi_clk_prepare(bool FLAG) |
| { |
| void __iomem *ssi_base; |
| unsigned int value; |
| |
| ssi_base = ioremap(L4_34XX_BASE + OMAP_SSI_OFFSET, OMAP_SSI_SIZE); |
| if (!ssi_base) { |
| pr_err("%s: error, SSI not configured\n", __func__); |
| return; |
| } |
| |
| if (FLAG) { |
| /* Set Autoidle, SIDLEMode to smart idle, and MIDLEmode to |
| * no idle |
| */ |
| value = SSI_AUTOIDLE | SSI_SIDLE_SMARTIDLE | SSI_MIDLE_NOIDLE; |
| } else { |
| /* Set Autoidle, SIDLEMode to forced idle, and MIDLEmode to |
| * forced idle |
| */ |
| value = SSI_AUTOIDLE; |
| } |
| |
| __raw_writel(value, ssi_base + OMAP_SSI_SYSCONFIG_OFFSET); |
| iounmap(ssi_base); |
| } |
| |