diff options
author | Philippe Langlais <philippe.langlais@linaro.org> | 2011-07-05 14:40:21 +0200 |
---|---|---|
committer | Philippe Langlais <philippe.langlais@linaro.org> | 2011-07-22 15:51:08 +0200 |
commit | 1b91ff8943719a129cfc33bc9646b3a6122937b3 (patch) | |
tree | 8cc79140377f3946c61070be6410f313833a4fab /drivers/mfd/db5500-prcmu.c | |
parent | 9f36e7110eb201aafcd60c16d617d139db7fabbb (diff) |
ux500: fix PRCMU boot problem, use new MFD driver & code cleanup
Signed-off-by: Philippe Langlais <philippe.langlais@linaro.org>
Diffstat (limited to 'drivers/mfd/db5500-prcmu.c')
-rw-r--r-- | drivers/mfd/db5500-prcmu.c | 330 |
1 files changed, 327 insertions, 3 deletions
diff --git a/drivers/mfd/db5500-prcmu.c b/drivers/mfd/db5500-prcmu.c index 9dbb3cab4a6..9cd9da40d87 100644 --- a/drivers/mfd/db5500-prcmu.c +++ b/drivers/mfd/db5500-prcmu.c @@ -20,9 +20,9 @@ #include <linux/jiffies.h> #include <linux/bitops.h> #include <linux/interrupt.h> -#include <linux/mfd/db5500-prcmu.h> #include <mach/hardware.h> #include <mach/irqs.h> +#include <mach/prcmu.h> #include <mach/db5500-regs.h> #include "db5500-prcmu-regs.h" @@ -64,6 +64,19 @@ #define PRCM_ACK_MB6 (tcdm_base + 0xF0C) #define PRCM_ACK_MB7 (tcdm_base + 0xF08) +/* Mailbox 2 REQs */ +#define PRCM_REQ_MB2_EPOD_CLIENT (PRCM_REQ_MB2 + 0x0) +#define PRCM_REQ_MB2_EPOD_STATE (PRCM_REQ_MB2 + 0x1) +#define PRCM_REQ_MB2_CLK_CLIENT (PRCM_REQ_MB2 + 0x2) +#define PRCM_REQ_MB2_CLK_STATE (PRCM_REQ_MB2 + 0x3) +#define PRCM_REQ_MB2_PLL_CLIENT (PRCM_REQ_MB2 + 0x4) +#define PRCM_REQ_MB2_PLL_STATE (PRCM_REQ_MB2 + 0x5) + +/* Mailbox 2 ACKs */ +#define PRCM_ACK_MB2_EPOD_STATUS (PRCM_ACK_MB2 + 0x2) +#define PRCM_ACK_MB2_CLK_STATUS (PRCM_ACK_MB2 + 0x6) +#define PRCM_ACK_MB2_PLL_STATUS (PRCM_ACK_MB2 + 0xA) + enum mb_return_code { RC_SUCCESS, RC_FAIL, @@ -80,11 +93,29 @@ enum mb0_header { }; /* Mailbox 5 headers. */ +enum mb2_header { + MB2H_EPOD_REQUEST = 1, + MB2H_CLK_REQUEST, + MB2H_PLL_REQUEST, +}; + +/* Mailbox 5 headers. */ enum mb5_header { MB5H_I2C_WRITE = 1, MB5H_I2C_READ, }; +enum epod_state { + EPOD_OFF, + EPOD_ON, +}; +enum db5500_prcmu_pll { + DB5500_PLL_SOC0, + DB5500_PLL_SOC1, + DB5500_PLL_DDR, + DB5500_NUM_PLL_ID, +}; + /* Request mailbox 5 fields. */ #define PRCM_REQ_MB5_I2C_SLAVE (PRCM_REQ_MB5 + 0) #define PRCM_REQ_MB5_I2C_REG (PRCM_REQ_MB5 + 1) @@ -109,15 +140,18 @@ enum mb5_header { #define PRCMU_DSI_CLOCK_SETTING 0x00000128 /* TVCLK_MGT PLLSW=001 (PLLSOC0) PLLDIV=0x13, = 19.05 MHZ */ #define PRCMU_DSI_LP_CLOCK_SETTING 0x00000135 -#define PRCMU_PLLDSI_FREQ_SETTING 0x0004013C +#define PRCMU_PLLDSI_FREQ_SETTING 0x00020121 #define PRCMU_DSI_PLLOUT_SEL_SETTING 0x00000002 -#define PRCMU_ENABLE_ESCAPE_CLOCK_DIV 0x03000101 +#define PRCMU_ENABLE_ESCAPE_CLOCK_DIV 0x03000201 #define PRCMU_DISABLE_ESCAPE_CLOCK_DIV 0x00000101 #define PRCMU_ENABLE_PLLDSI 0x00000001 #define PRCMU_DISABLE_PLLDSI 0x00000000 #define PRCMU_DSI_RESET_SW 0x00000003 +#define PRCMU_RESOUTN0_PIN 0x00000001 +#define PRCMU_RESOUTN1_PIN 0x00000002 +#define PRCMU_RESOUTN2_PIN 0x00000004 #define PRCMU_PLLDSI_LOCKP_LOCKED 0x3 @@ -130,6 +164,26 @@ static struct { } mb0_transfer; /* + * mb2_transfer - state needed for mailbox 2 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @req: Request data that need to persist between requests. + * @ack: Reply ("acknowledge") data. + */ +static struct { + struct mutex lock; + struct completion work; + struct { + u8 epod_states[DB5500_NUM_EPOD_ID]; + u8 pll_states[DB5500_NUM_PLL_ID]; + } req; + struct { + u8 header; + u8 status; + } ack; +} mb2_transfer; + +/* * mb5_transfer - state needed for mailbox 5 communication. * @lock: The transaction lock. * @work: The transaction completion structure. @@ -148,6 +202,159 @@ static struct { /* PRCMU TCDM base IO address. */ static __iomem void *tcdm_base; +struct clk_mgt { + unsigned int offset; + u32 pllsw; +}; + +static DEFINE_SPINLOCK(clk_mgt_lock); + +#define CLK_MGT_ENTRY(_name)[PRCMU_##_name] = { \ + (PRCM_##_name##_MGT_OFF), 0 \ +} +static struct clk_mgt clk_mgt[PRCMU_NUM_REG_CLOCKS] = { + CLK_MGT_ENTRY(SGACLK), + CLK_MGT_ENTRY(UARTCLK), + CLK_MGT_ENTRY(MSP02CLK), + CLK_MGT_ENTRY(I2CCLK), + CLK_MGT_ENTRY(SDMMCCLK), + CLK_MGT_ENTRY(PER1CLK), + CLK_MGT_ENTRY(PER2CLK), + CLK_MGT_ENTRY(PER3CLK), + CLK_MGT_ENTRY(PER5CLK), + CLK_MGT_ENTRY(PER6CLK), + CLK_MGT_ENTRY(PWMCLK), + CLK_MGT_ENTRY(IRDACLK), + CLK_MGT_ENTRY(IRRCCLK), + CLK_MGT_ENTRY(HDMICLK), + CLK_MGT_ENTRY(APEATCLK), + CLK_MGT_ENTRY(APETRACECLK), + CLK_MGT_ENTRY(MCDECLK), + CLK_MGT_ENTRY(DSIALTCLK), + CLK_MGT_ENTRY(DMACLK), + CLK_MGT_ENTRY(B2R2CLK), + CLK_MGT_ENTRY(TVCLK), + CLK_MGT_ENTRY(RNGCLK), + CLK_MGT_ENTRY(SIACLK), + CLK_MGT_ENTRY(SVACLK), +}; + +static int request_timclk(bool enable) +{ + u32 val = (PRCM_TCR_DOZE_MODE | PRCM_TCR_TENSEL_MASK); + + if (!enable) + val |= PRCM_TCR_STOP_TIMERS; + writel(val, (_PRCMU_BASE + PRCM_TCR)); + + return 0; +} + +static int request_reg_clock(u8 clock, bool enable) +{ + u32 val; + unsigned long flags; + + WARN_ON(!clk_mgt[clock].offset); + + spin_lock_irqsave(&clk_mgt_lock, flags); + + /* Grab the HW semaphore. */ + while ((readl(_PRCMU_BASE + PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + val = readl(_PRCMU_BASE + clk_mgt[clock].offset); + if (enable) { + val |= (PRCM_CLK_MGT_CLKEN | clk_mgt[clock].pllsw); + } else { + clk_mgt[clock].pllsw = (val & PRCM_CLK_MGT_CLKPLLSW_MASK); + val &= ~(PRCM_CLK_MGT_CLKEN | PRCM_CLK_MGT_CLKPLLSW_MASK); + } + writel(val, (_PRCMU_BASE + clk_mgt[clock].offset)); + + /* Release the HW semaphore. */ + writel(0, (_PRCMU_BASE + PRCM_SEM)); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); + + return 0; +} + +/* + * request_pll() - Request for a pll to be enabled or disabled. + * @pll: The pll for which the request is made. + * @enable: Whether the clock should be enabled (true) or disabled (false). + * + * This function should only be used by the clock implementation. + * Do not use it from any other place! + */ +static int request_pll(u8 pll, bool enable) +{ + int r = 0; + + BUG_ON(pll >= DB5500_NUM_PLL_ID); + mutex_lock(&mb2_transfer.lock); + + while (readl(PRCM_MBOX_CPU_VAL & MBOX_BIT(2)) + cpu_relax(); + + mb2_transfer.req.pll_states[pll] = enable; + + /* fill in mailbox */ + writeb(pll, PRCM_REQ_MB2_PLL_CLIENT); + writeb(mb2_transfer.req.pll_states[pll], PRCM_REQ_MB2_PLL_STATE); + + writeb(MB2H_PLL_REQUEST, PRCM_REQ_MB2_HEADER); + + writel(MBOX_BIT(2), PRCM_MBOX_CPU_SET); + if (!wait_for_completion_timeout(&mb2_transfer.work, + msecs_to_jiffies(500))) { + pr_err("prcmu: set_pll() failed.\n" + "prcmu: Please check your firmware version.\n"); + r = -EIO; + WARN(1, "Failed to set pll"); + goto unlock_and_return; + } + if (mb2_transfer.ack.status != RC_SUCCESS || + mb2_transfer.ack.header != MB2H_PLL_REQUEST) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb2_transfer.lock); + + return r; +} + +void prcmu_enable_wakeups(u32 wakeups) +{ +} + +/** + * prcmu_request_clock() - Request for a clock to be enabled or disabled. + * @clock: The clock for which the request is made. + * @enable: Whether the clock should be enabled (true) or disabled (false). + * + * This function should only be used by the clock implementation. + * Do not use it from any other place! + */ +int prcmu_request_clock(u8 clock, bool enable) +{ + if (clock < PRCMU_NUM_REG_CLOCKS) + return request_reg_clock(clock, enable); + else if (clock == PRCMU_TIMCLK) + return request_timclk(enable); + else if (clock == PRCMU_PLLSOC0) + return request_pll(DB5500_PLL_SOC0, enable); + else if (clock == PRCMU_PLLSOC1) + return request_pll(DB5500_PLL_SOC1, enable); + else if (clock == PRCMU_PLLDDR) + return request_pll(DB5500_PLL_DDR, enable); + else if (clock == PRCMU_SYSCLK) + return -EINVAL; + else + return -EINVAL; +} + /** * db5500_prcmu_abb_read() - Read register value(s) from the ABB. * @slave: The I2C slave address. @@ -230,6 +437,34 @@ int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) return r; } +int prcmu_resetout(u8 resoutn, u8 state) +{ + int offset; + int pin = -1; + + offset = state > 0 ? PRCM_RESOUTN_SET_OFFSET : PRCM_RESOUTN_CLR_OFFSET; + + switch (resoutn) { + case 0: + pin = PRCMU_RESOUTN0_PIN; + break; + case 1: + pin = PRCMU_RESOUTN1_PIN; + break; + case 2: + pin = PRCMU_RESOUTN2_PIN; + default: + break; + } + + if (pin > 0) + writel(pin, _PRCMU_BASE + offset); + else + return -EINVAL; + + return 0; +} + int db5500_prcmu_enable_dsipll(void) { int i; @@ -294,6 +529,72 @@ static void ack_dbb_wakeup(void) spin_unlock_irqrestore(&mb0_transfer.lock, flags); } +int prcmu_set_epod(u16 epod_id, u8 epod_state) +{ + int r = 0; + bool ram_retention = false; + + /* check argument */ + BUG_ON(epod_id < DB5500_EPOD_ID_BASE); + BUG_ON(epod_state > EPOD_STATE_ON); + + epod_id &= 0xFF; + BUG_ON(epod_id > DB5500_NUM_EPOD_ID); + + /* TODO: FW does not take retention for ESRAM12? + set flag if retention is possible */ + switch (epod_id) { + case DB5500_EPOD_ID_ESRAM12: + ram_retention = true; + break; + } + + /* check argument */ + /* BUG_ON(epod_state == EPOD_STATE_RAMRET && !ram_retention); */ + + /* get lock */ + mutex_lock(&mb2_transfer.lock); + + /* wait for mailbox */ + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(2)) + cpu_relax(); + + /* PRCMU FW can only handle on or off */ + if (epod_state == EPOD_STATE_ON) + mb2_transfer.req.epod_states[epod_id] = EPOD_ON; + else if (epod_state == EPOD_STATE_OFF) + mb2_transfer.req.epod_states[epod_id] = EPOD_OFF; + else { + r = -EINVAL; + goto unlock_and_return; + } + + /* fill in mailbox */ + writeb(epod_id, PRCM_REQ_MB2_EPOD_CLIENT); + writeb(mb2_transfer.req.epod_states[epod_id], PRCM_REQ_MB2_EPOD_STATE); + + writeb(MB2H_EPOD_REQUEST, PRCM_REQ_MB2_HEADER); + + writel(MBOX_BIT(2), PRCM_MBOX_CPU_SET); + + if (!wait_for_completion_timeout(&mb2_transfer.work, + msecs_to_jiffies(500))) { + pr_err("prcmu: set_epod() failed.\n" + "prcmu: Please check your firmware version.\n"); + r = -EIO; + WARN(1, "Failed to set epod"); + goto unlock_and_return; + } + + if (mb2_transfer.ack.status != RC_SUCCESS || + mb2_transfer.ack.header != MB2H_EPOD_REQUEST) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb2_transfer.lock); + return r; +} + static inline void print_unknown_header_warning(u8 n, u8 header) { pr_warning("prcmu: Unknown message header (%d) in mailbox %d.\n", @@ -327,7 +628,28 @@ static bool read_mailbox_1(void) static bool read_mailbox_2(void) { + u8 header; + + header = readb(PRCM_ACK_MB2_HEADER); + mb2_transfer.ack.header = header; + switch (header) { + case MB2H_EPOD_REQUEST: + mb2_transfer.ack.status = readb(PRCM_ACK_MB2_EPOD_STATUS); + break; + case MB2H_CLK_REQUEST: + mb2_transfer.ack.status = readb(PRCM_ACK_MB2_CLK_STATUS); + break; + case MB2H_PLL_REQUEST: + mb2_transfer.ack.status = readb(PRCM_ACK_MB2_PLL_STATUS); + break; + default: + writel(MBOX_BIT(2), PRCM_ARM_IT1_CLEAR); + pr_err("prcmu: Wrong ACK received for MB2 request \n"); + return false; + break; + } writel(MBOX_BIT(2), PRCM_ARM_IT1_CLEAR); + complete(&mb2_transfer.work); return false; } @@ -418,7 +740,9 @@ void __init db5500_prcmu_early_init(void) { tcdm_base = __io_address(U5500_PRCMU_TCDM_BASE); spin_lock_init(&mb0_transfer.lock); + mutex_init(&mb2_transfer.lock); mutex_init(&mb5_transfer.lock); + init_completion(&mb2_transfer.work); init_completion(&mb5_transfer.work); } |