| /* | |
| * 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); |