| /* |
| * Copyright (C) ST-Ericsson SA 2010 |
| * |
| * License Terms: GNU General Public License v2 |
| * Author: Mattias Nilsson <mattias.i.nilsson@stericsson.com> |
| * |
| * U5500 PRCM Unit interface driver |
| */ |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/delay.h> |
| #include <linux/errno.h> |
| #include <linux/err.h> |
| #include <linux/spinlock.h> |
| #include <linux/io.h> |
| #include <linux/slab.h> |
| #include <linux/mutex.h> |
| #include <linux/completion.h> |
| #include <linux/irq.h> |
| #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/db5500-regs.h> |
| #include "db5500-prcmu-regs.h" |
| |
| #define _PRCM_MB_HEADER (tcdm_base + 0xFE8) |
| #define PRCM_REQ_MB0_HEADER (_PRCM_MB_HEADER + 0x0) |
| #define PRCM_REQ_MB1_HEADER (_PRCM_MB_HEADER + 0x1) |
| #define PRCM_REQ_MB2_HEADER (_PRCM_MB_HEADER + 0x2) |
| #define PRCM_REQ_MB3_HEADER (_PRCM_MB_HEADER + 0x3) |
| #define PRCM_REQ_MB4_HEADER (_PRCM_MB_HEADER + 0x4) |
| #define PRCM_REQ_MB5_HEADER (_PRCM_MB_HEADER + 0x5) |
| #define PRCM_REQ_MB6_HEADER (_PRCM_MB_HEADER + 0x6) |
| #define PRCM_REQ_MB7_HEADER (_PRCM_MB_HEADER + 0x7) |
| #define PRCM_ACK_MB0_HEADER (_PRCM_MB_HEADER + 0x8) |
| #define PRCM_ACK_MB1_HEADER (_PRCM_MB_HEADER + 0x9) |
| #define PRCM_ACK_MB2_HEADER (_PRCM_MB_HEADER + 0xa) |
| #define PRCM_ACK_MB3_HEADER (_PRCM_MB_HEADER + 0xb) |
| #define PRCM_ACK_MB4_HEADER (_PRCM_MB_HEADER + 0xc) |
| #define PRCM_ACK_MB5_HEADER (_PRCM_MB_HEADER + 0xd) |
| #define PRCM_ACK_MB6_HEADER (_PRCM_MB_HEADER + 0xe) |
| #define PRCM_ACK_MB7_HEADER (_PRCM_MB_HEADER + 0xf) |
| |
| /* Req Mailboxes */ |
| #define PRCM_REQ_MB0 (tcdm_base + 0xFD8) |
| #define PRCM_REQ_MB1 (tcdm_base + 0xFCC) |
| #define PRCM_REQ_MB2 (tcdm_base + 0xFC4) |
| #define PRCM_REQ_MB3 (tcdm_base + 0xFC0) |
| #define PRCM_REQ_MB4 (tcdm_base + 0xF98) |
| #define PRCM_REQ_MB5 (tcdm_base + 0xF90) |
| #define PRCM_REQ_MB6 (tcdm_base + 0xF8C) |
| #define PRCM_REQ_MB7 (tcdm_base + 0xF84) |
| |
| /* Ack Mailboxes */ |
| #define PRCM_ACK_MB0 (tcdm_base + 0xF38) |
| #define PRCM_ACK_MB1 (tcdm_base + 0xF30) |
| #define PRCM_ACK_MB2 (tcdm_base + 0xF24) |
| #define PRCM_ACK_MB3 (tcdm_base + 0xF20) |
| #define PRCM_ACK_MB4 (tcdm_base + 0xF1C) |
| #define PRCM_ACK_MB5 (tcdm_base + 0xF14) |
| #define PRCM_ACK_MB6 (tcdm_base + 0xF0C) |
| #define PRCM_ACK_MB7 (tcdm_base + 0xF08) |
| |
| enum mb_return_code { |
| RC_SUCCESS, |
| RC_FAIL, |
| }; |
| |
| /* Mailbox 0 headers. */ |
| enum mb0_header { |
| /* request */ |
| RMB0H_PWR_STATE_TRANS = 1, |
| RMB0H_WAKE_UP_CFG, |
| RMB0H_RD_WAKE_UP_ACK, |
| /* acknowledge */ |
| AMB0H_WAKE_UP = 1, |
| }; |
| |
| /* Mailbox 5 headers. */ |
| enum mb5_header { |
| MB5H_I2C_WRITE = 1, |
| MB5H_I2C_READ, |
| }; |
| |
| /* Request mailbox 5 fields. */ |
| #define PRCM_REQ_MB5_I2C_SLAVE (PRCM_REQ_MB5 + 0) |
| #define PRCM_REQ_MB5_I2C_REG (PRCM_REQ_MB5 + 1) |
| #define PRCM_REQ_MB5_I2C_SIZE (PRCM_REQ_MB5 + 2) |
| #define PRCM_REQ_MB5_I2C_DATA (PRCM_REQ_MB5 + 4) |
| |
| /* Acknowledge mailbox 5 fields. */ |
| #define PRCM_ACK_MB5_RETURN_CODE (PRCM_ACK_MB5 + 0) |
| #define PRCM_ACK_MB5_I2C_DATA (PRCM_ACK_MB5 + 4) |
| |
| #define NUM_MB 8 |
| #define MBOX_BIT BIT |
| #define ALL_MBOX_BITS (MBOX_BIT(NUM_MB) - 1) |
| |
| /* |
| * Used by MCDE to setup all necessary PRCMU registers |
| */ |
| #define PRCMU_RESET_DSIPLL 0x00004000 |
| #define PRCMU_UNCLAMP_DSIPLL 0x00400800 |
| |
| /* HDMI CLK MGT PLLSW=001 (PLLSOC0), PLLDIV=0x8, = 50 Mhz*/ |
| #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_DSI_PLLOUT_SEL_SETTING 0x00000002 |
| #define PRCMU_ENABLE_ESCAPE_CLOCK_DIV 0x03000101 |
| #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_PLLDSI_LOCKP_LOCKED 0x3 |
| |
| /* |
| * mb0_transfer - state needed for mailbox 0 communication. |
| * @lock: The transaction lock. |
| */ |
| static struct { |
| spinlock_t lock; |
| } mb0_transfer; |
| |
| /* |
| * mb5_transfer - state needed for mailbox 5 communication. |
| * @lock: The transaction lock. |
| * @work: The transaction completion structure. |
| * @ack: Reply ("acknowledge") data. |
| */ |
| static struct { |
| struct mutex lock; |
| struct completion work; |
| struct { |
| u8 header; |
| u8 status; |
| u8 value[4]; |
| } ack; |
| } mb5_transfer; |
| |
| /* PRCMU TCDM base IO address. */ |
| static __iomem void *tcdm_base; |
| |
| /** |
| * db5500_prcmu_abb_read() - Read register value(s) from the ABB. |
| * @slave: The I2C slave address. |
| * @reg: The (start) register address. |
| * @value: The read out value(s). |
| * @size: The number of registers to read. |
| * |
| * Reads register value(s) from the ABB. |
| * @size has to be <= 4. |
| */ |
| int db5500_prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) |
| { |
| int r; |
| |
| if ((size < 1) || (4 < size)) |
| return -EINVAL; |
| |
| mutex_lock(&mb5_transfer.lock); |
| |
| while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) |
| cpu_relax(); |
| writeb(slave, PRCM_REQ_MB5_I2C_SLAVE); |
| writeb(reg, PRCM_REQ_MB5_I2C_REG); |
| writeb(size, PRCM_REQ_MB5_I2C_SIZE); |
| writeb(MB5H_I2C_READ, PRCM_REQ_MB5_HEADER); |
| |
| writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); |
| wait_for_completion(&mb5_transfer.work); |
| |
| r = 0; |
| if ((mb5_transfer.ack.header == MB5H_I2C_READ) && |
| (mb5_transfer.ack.status == RC_SUCCESS)) |
| memcpy(value, mb5_transfer.ack.value, (size_t)size); |
| else |
| r = -EIO; |
| |
| mutex_unlock(&mb5_transfer.lock); |
| |
| return r; |
| } |
| |
| /** |
| * db5500_prcmu_abb_write() - Write register value(s) to the ABB. |
| * @slave: The I2C slave address. |
| * @reg: The (start) register address. |
| * @value: The value(s) to write. |
| * @size: The number of registers to write. |
| * |
| * Writes register value(s) to the ABB. |
| * @size has to be <= 4. |
| */ |
| int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) |
| { |
| int r; |
| |
| if ((size < 1) || (4 < size)) |
| return -EINVAL; |
| |
| mutex_lock(&mb5_transfer.lock); |
| |
| while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) |
| cpu_relax(); |
| writeb(slave, PRCM_REQ_MB5_I2C_SLAVE); |
| writeb(reg, PRCM_REQ_MB5_I2C_REG); |
| writeb(size, PRCM_REQ_MB5_I2C_SIZE); |
| memcpy_toio(PRCM_REQ_MB5_I2C_DATA, value, size); |
| writeb(MB5H_I2C_WRITE, PRCM_REQ_MB5_HEADER); |
| |
| writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); |
| wait_for_completion(&mb5_transfer.work); |
| |
| if ((mb5_transfer.ack.header == MB5H_I2C_WRITE) && |
| (mb5_transfer.ack.status == RC_SUCCESS)) |
| r = 0; |
| else |
| r = -EIO; |
| |
| mutex_unlock(&mb5_transfer.lock); |
| |
| return r; |
| } |
| |
| int db5500_prcmu_enable_dsipll(void) |
| { |
| int i; |
| |
| /* Enable DSIPLL_RESETN resets */ |
| writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_CLR); |
| /* Unclamp DSIPLL in/out */ |
| writel(PRCMU_UNCLAMP_DSIPLL, PRCM_MMIP_LS_CLAMP_CLR); |
| /* Set DSI PLL FREQ */ |
| writel(PRCMU_PLLDSI_FREQ_SETTING, PRCM_PLLDSI_FREQ); |
| writel(PRCMU_DSI_PLLOUT_SEL_SETTING, |
| PRCM_DSI_PLLOUT_SEL); |
| /* Enable Escape clocks */ |
| writel(PRCMU_ENABLE_ESCAPE_CLOCK_DIV, PRCM_DSITVCLK_DIV); |
| |
| /* Start DSI PLL */ |
| writel(PRCMU_ENABLE_PLLDSI, PRCM_PLLDSI_ENABLE); |
| /* Reset DSI PLL */ |
| writel(PRCMU_DSI_RESET_SW, PRCM_DSI_SW_RESET); |
| for (i = 0; i < 10; i++) { |
| if ((readl(PRCM_PLLDSI_LOCKP) & |
| PRCMU_PLLDSI_LOCKP_LOCKED) == PRCMU_PLLDSI_LOCKP_LOCKED) |
| break; |
| udelay(100); |
| } |
| /* Release DSIPLL_RESETN */ |
| writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_SET); |
| return 0; |
| } |
| |
| int db5500_prcmu_disable_dsipll(void) |
| { |
| /* Disable dsi pll */ |
| writel(PRCMU_DISABLE_PLLDSI, PRCM_PLLDSI_ENABLE); |
| /* Disable escapeclock */ |
| writel(PRCMU_DISABLE_ESCAPE_CLOCK_DIV, PRCM_DSITVCLK_DIV); |
| return 0; |
| } |
| |
| int db5500_prcmu_set_display_clocks(void) |
| { |
| /* HDMI and TVCLK Should be handled somewhere else */ |
| /* PLLDIV=8, PLLSW=2, CLKEN=1 */ |
| writel(PRCMU_DSI_CLOCK_SETTING, PRCM_HDMICLK_MGT); |
| /* PLLDIV=14, PLLSW=2, CLKEN=1 */ |
| writel(PRCMU_DSI_LP_CLOCK_SETTING, PRCM_TVCLK_MGT); |
| return 0; |
| } |
| |
| static void ack_dbb_wakeup(void) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&mb0_transfer.lock, flags); |
| |
| while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) |
| cpu_relax(); |
| |
| writeb(RMB0H_RD_WAKE_UP_ACK, PRCM_REQ_MB0_HEADER); |
| writel(MBOX_BIT(0), PRCM_MBOX_CPU_SET); |
| |
| spin_unlock_irqrestore(&mb0_transfer.lock, flags); |
| } |
| |
| static inline void print_unknown_header_warning(u8 n, u8 header) |
| { |
| pr_warning("prcmu: Unknown message header (%d) in mailbox %d.\n", |
| header, n); |
| } |
| |
| static bool read_mailbox_0(void) |
| { |
| bool r; |
| u8 header; |
| |
| header = readb(PRCM_ACK_MB0_HEADER); |
| switch (header) { |
| case AMB0H_WAKE_UP: |
| r = true; |
| break; |
| default: |
| print_unknown_header_warning(0, header); |
| r = false; |
| break; |
| } |
| writel(MBOX_BIT(0), PRCM_ARM_IT1_CLEAR); |
| return r; |
| } |
| |
| static bool read_mailbox_1(void) |
| { |
| writel(MBOX_BIT(1), PRCM_ARM_IT1_CLEAR); |
| return false; |
| } |
| |
| static bool read_mailbox_2(void) |
| { |
| writel(MBOX_BIT(2), PRCM_ARM_IT1_CLEAR); |
| return false; |
| } |
| |
| static bool read_mailbox_3(void) |
| { |
| writel(MBOX_BIT(3), PRCM_ARM_IT1_CLEAR); |
| return false; |
| } |
| |
| static bool read_mailbox_4(void) |
| { |
| writel(MBOX_BIT(4), PRCM_ARM_IT1_CLEAR); |
| return false; |
| } |
| |
| static bool read_mailbox_5(void) |
| { |
| u8 header; |
| |
| header = readb(PRCM_ACK_MB5_HEADER); |
| switch (header) { |
| case MB5H_I2C_READ: |
| memcpy_fromio(mb5_transfer.ack.value, PRCM_ACK_MB5_I2C_DATA, 4); |
| case MB5H_I2C_WRITE: |
| mb5_transfer.ack.header = header; |
| mb5_transfer.ack.status = readb(PRCM_ACK_MB5_RETURN_CODE); |
| complete(&mb5_transfer.work); |
| break; |
| default: |
| print_unknown_header_warning(5, header); |
| break; |
| } |
| writel(MBOX_BIT(5), PRCM_ARM_IT1_CLEAR); |
| return false; |
| } |
| |
| static bool read_mailbox_6(void) |
| { |
| writel(MBOX_BIT(6), PRCM_ARM_IT1_CLEAR); |
| return false; |
| } |
| |
| static bool read_mailbox_7(void) |
| { |
| writel(MBOX_BIT(7), PRCM_ARM_IT1_CLEAR); |
| return false; |
| } |
| |
| static bool (* const read_mailbox[NUM_MB])(void) = { |
| read_mailbox_0, |
| read_mailbox_1, |
| read_mailbox_2, |
| read_mailbox_3, |
| read_mailbox_4, |
| read_mailbox_5, |
| read_mailbox_6, |
| read_mailbox_7 |
| }; |
| |
| static irqreturn_t prcmu_irq_handler(int irq, void *data) |
| { |
| u32 bits; |
| u8 n; |
| irqreturn_t r; |
| |
| bits = (readl(PRCM_ARM_IT1_VAL) & ALL_MBOX_BITS); |
| if (unlikely(!bits)) |
| return IRQ_NONE; |
| |
| r = IRQ_HANDLED; |
| for (n = 0; bits; n++) { |
| if (bits & MBOX_BIT(n)) { |
| bits -= MBOX_BIT(n); |
| if (read_mailbox[n]()) |
| r = IRQ_WAKE_THREAD; |
| } |
| } |
| return r; |
| } |
| |
| static irqreturn_t prcmu_irq_thread_fn(int irq, void *data) |
| { |
| ack_dbb_wakeup(); |
| return IRQ_HANDLED; |
| } |
| |
| void __init db5500_prcmu_early_init(void) |
| { |
| tcdm_base = __io_address(U5500_PRCMU_TCDM_BASE); |
| spin_lock_init(&mb0_transfer.lock); |
| mutex_init(&mb5_transfer.lock); |
| init_completion(&mb5_transfer.work); |
| } |
| |
| /** |
| * prcmu_fw_init - arch init call for the Linux PRCMU fw init logic |
| * |
| */ |
| int __init db5500_prcmu_init(void) |
| { |
| int r = 0; |
| |
| if (ux500_is_svp() || !cpu_is_u5500()) |
| return -ENODEV; |
| |
| /* Clean up the mailbox interrupts after pre-kernel code. */ |
| writel(ALL_MBOX_BITS, PRCM_ARM_IT1_CLEAR); |
| |
| r = request_threaded_irq(IRQ_DB5500_PRCMU1, prcmu_irq_handler, |
| prcmu_irq_thread_fn, 0, "prcmu", NULL); |
| if (r < 0) { |
| pr_err("prcmu: Failed to allocate IRQ_DB5500_PRCMU1.\n"); |
| return -EBUSY; |
| } |
| return 0; |
| } |
| |
| arch_initcall(db5500_prcmu_init); |