diff options
| author | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
| commit | 8e4bff4cab0ddac6060645b0715210484d02ff40 (patch) | |
| tree | 3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/platform/sprd/adi.c | |
Diffstat (limited to 'drivers/platform/sprd/adi.c')
| -rw-r--r-- | drivers/platform/sprd/adi.c | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/drivers/platform/sprd/adi.c b/drivers/platform/sprd/adi.c new file mode 100644 index 00000000..9fc9f30e --- /dev/null +++ b/drivers/platform/sprd/adi.c @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * Copyright (C) 2012 steve zhan + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/** +*For arm read ,delay about 1us when clk_adi runs at 76.8M +*The interface timing is compatible with spi timing +*/ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/irqflags.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/hwspinlock.h> +#include <asm/delay.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include <soc/sprd/sci_glb_regs.h> +#include <soc/sprd/arch_lock.h> +#include <soc/sprd/adi.h> + +unsigned long sprd_adi_base; +unsigned long sprd_adi_phys; + +/* soc defined begin*/ +#define CTL_ADI_BASE (SPRD_ADI_BASE) + +/* registers definitions for controller CTL_ADI */ +#define REG_ADI_CTRL0 (CTL_ADI_BASE + 0x04) +#ifdef CONFIG_ARCH_SCX35LT8 +#define REG_ADI_CHNL_PRIL (CTL_ADI_BASE + 0x08) +#define REG_ADI_CHNL_PRIH (CTL_ADI_BASE + 0x0C) +#define REG_ADI_INT_RAW (CTL_ADI_BASE + 0x14) +#define REG_ADI_RD_CMD (CTL_ADI_BASE + 0x28) +#define REG_ADI_RD_DATA (CTL_ADI_BASE + 0x2C) +#define REG_ADI_FIFO_STS (CTL_ADI_BASE + 0x30) +#define REG_ADI_GSSI_CFG0 (CTL_ADI_BASE + 0x20) +#define REG_ADI_GSSI_CFG1 (CTL_ADI_BASE + 0x24) +#else +#define REG_ADI_CHNL_PRI (CTL_ADI_BASE + 0x08) +#define REG_ADI_INT_RAW (CTL_ADI_BASE + 0x10) +#define REG_ADI_RD_CMD (CTL_ADI_BASE + 0x24) +#define REG_ADI_RD_DATA (CTL_ADI_BASE + 0x28) +#define REG_ADI_FIFO_STS (CTL_ADI_BASE + 0x2c) +#define REG_ADI_GSSI_CFG0 (CTL_ADI_BASE + 0x1C) +#define REG_ADI_GSSI_CFG1 (CTL_ADI_BASE + 0x20) +#endif +/* bits definitions for register REG_ADI_CTRL0 */ +#define BIT_ARM_SCLK_EN ( BIT(1) ) +#define BITS_CMMB_WR_PRI ( (1) << 4 & (BIT(4)|BIT(5)) ) + +/* bits definitions for register REG_ADI_CHNL_PRI */ +#define BITS_PD_WR_PRI ( (1) << 14 & (BIT(14)|BIT(15)) ) +#define BITS_RFT_WR_PRI ( (1) << 12 & (BIT(12)|BIT(13)) ) +#define BITS_DSP_RD_PRI ( (2) << 10 & (BIT(10)|BIT(11)) ) +#define BITS_DSP_WR_PRI ( (2) << 8 & (BIT(8)|BIT(9)) ) +#define BITS_ARM_RD_PRI ( (3) << 6 & (BIT(6)|BIT(7)) ) +#define BITS_ARM_WR_PRI ( (3) << 4 & (BIT(4)|BIT(5)) ) +#define BITS_STC_WR_PRI ( (1) << 2 & (BIT(2)|BIT(3)) ) +#define BITS_INT_STEAL_PRI ( (3) << 0 & (BIT(0)|BIT(1)) ) + +/* bits definitions for register REG_ADI_RD_DATA */ +#define BIT_RD_CMD_BUSY ( BIT(31) ) +#define SHIFT_RD_ADDR ( 16 ) + +#define SHIFT_RD_VALU ( 0 ) +#define MASK_RD_VALU ( 0xFFFF ) + +/* bits definitions for register REG_ADI_FIFO_STS */ +#define BIT_FIFO_FULL ( BIT(11) ) +#define FIFO_IS_FULL() (__raw_readl((void __iomem *)REG_ADI_FIFO_STS) & BIT_FIFO_FULL) +#define BIT_FIFO_EMPTY ( BIT(10) ) + +/* special V1 (sc8830 soc) defined */ +/* bits definitions for register REG_ADI_CTRL0 */ +#define BIT_ADI_WR(_X_) ( (_X_) << 2 ) +#define BITS_ADDR_BYTE_SEL(_X_) ( (_X_) << 0 & (BIT(0)|BIT(1)) ) + +/* bits definitions for register REG_ADI_CHNL_PRI */ +#define VALUE_CH_PRI (0x0) + + +#if defined(CONFIG_ARCH_SCX35) +#define ANA_VIRT_BASE ( REGS_ADISLAVE_BASE ) +#define ANA_PHYS_BASE ( REGS_ADISLAVE_PHYS ) +#else +#define ANA_VIRT_BASE ( REGS_MISC_BASE ) +#define ANA_PHYS_BASE ( REGS_MISC_PHYS ) +#endif + +#define ANA_ADDR_SIZE (SZ_4K) + +#define TO_ADDR(_x_) ( ((_x_) >> SHIFT_RD_ADDR) & readback_addr_mak ) +/* soc defined end*/ + +/*This value have a bit of depending on real hardware fifo size*/ +#define CACHE_SIZE (16) +#define HEAD_ADD (1) +#define TAIL_ADD (0) + +static struct __data { + unsigned long reg; + u16 val; +} __data_array[CACHE_SIZE]; + +static struct __data *head_p = &__data_array[0]; +static struct __data *tail_p = &__data_array[0]; +static u32 data_in_cache = 0; +static u32 readback_addr_mak __read_mostly = 0; + +/*FIXME: Now define adi IP version, sc8825 is zero, sc8830 is one, +* Adi need init early that than read soc id, now using this ARCH dependency. +*/ +static inline int __adi_ver(void) +{ +#ifdef CONFIG_ARCH_SC8825 + return 0; +#else + return 1; +#endif +} + +static inline int __adi_fifo_drain(void) +{ + int cnt = 1000; + while (!(__raw_readl((void __iomem *)REG_ADI_FIFO_STS) & BIT_FIFO_EMPTY) && cnt--) { + udelay(1); + } + WARN(cnt == 0, "ADI WAIT timeout!!!"); + return 0; +} + +int sci_is_adi_vaddr(unsigned long vaddr) +{ + return (vaddr >= ANA_VIRT_BASE && vaddr <= (ANA_VIRT_BASE + ANA_ADDR_SIZE)); +} +EXPORT_SYMBOL(sci_is_adi_vaddr); + +int sci_adi_p2v(unsigned long paddr, unsigned long *vaddr) +{ + if(paddr < ANA_PHYS_BASE || paddr > (ANA_PHYS_BASE + ANA_ADDR_SIZE)) { + return -1; + } else { + *vaddr = paddr - ANA_PHYS_BASE + ANA_VIRT_BASE; + } + return 0; +} +EXPORT_SYMBOL(sci_adi_p2v); + +static inline int __adi_addr_check(unsigned long vaddr) +{ + if(!sci_is_adi_vaddr(vaddr)) { + WARN_ONCE(1, "Maybe ADI vaddr is wrong?!!"); + return -1; + } + return 0; +} + +static inline unsigned long __adi_translate_addr(unsigned long regvddr) +{ + regvddr = regvddr - ANA_VIRT_BASE + ANA_PHYS_BASE; + return regvddr; +} + +static inline int __adi_read(unsigned long regPddr, unsigned int *v) +{ + unsigned long val; + int cnt = 2000; + int retry_cnt = 0; + + /* + * We don't wait write fifo empty in here, + * Because if normal write is SYNC, that will + * wait fifo empty at the end of the write. + */ +re_try: + __raw_writel((u32)regPddr,(void __iomem *)REG_ADI_RD_CMD); + + /* + * wait read operation complete, RD_data[31] + * is set simulaneously when writing read command. + * RD_data[31] will be cleared after the read operation complete, + */ + do { + val = __raw_readl((void __iomem *)REG_ADI_RD_DATA); + } while ((val & BIT_RD_CMD_BUSY) && cnt--); + + WARN(cnt == 0, "ADI READ timeout!!!"); + /* val high part should be the address of the last read operation */ + if ((!v) || TO_ADDR(val) != (regPddr & readback_addr_mak)) { + printk("Warning:val = 0x%lx, regPaddr = 0x%lx, readback_addr_mak = 0x%x\n", + val,regPddr,readback_addr_mak); + if(retry_cnt++ < 5) + goto re_try; + else + return -1; + } + + *v = val & MASK_RD_VALU; + return 0; +} + +int sci_adi_read(unsigned long reg) +{ + int val = 0; + if (!__adi_addr_check(reg)) { + unsigned long flags; + int ret = 0; + reg = __adi_translate_addr(reg); + __arch_default_lock(HWLOCK_ADI, &flags); + ret = __adi_read(reg, &val); + __arch_default_unlock(HWLOCK_ADI, &flags); + if (ret) { + printk("read error: reg = 0x%lx\n",reg); + BUG_ON(1); + } + } + return val; +} +EXPORT_SYMBOL(sci_adi_read); + +static inline void __p_add(struct __data **p, u32 isHead) +{ + if (++(*p) > &__data_array[CACHE_SIZE - 1]) + (*p) = &__data_array[0]; + if (head_p == tail_p) { + if (isHead == HEAD_ADD) { + data_in_cache = 0; + } else { + data_in_cache = CACHE_SIZE; + } + } else { + data_in_cache = 2; + } +} + +static inline int __adi_write(unsigned long reg, u16 val, u32 sync) +{ + tail_p->reg = reg; + tail_p->val = val; + __p_add(&tail_p, TAIL_ADD); + while (!FIFO_IS_FULL() && (data_in_cache != 0)) { + __raw_writel(head_p->val,(void __iomem *)head_p->reg); + __p_add(&head_p, HEAD_ADD); + } + + if (sync || data_in_cache == CACHE_SIZE) { + __adi_fifo_drain(); + while (data_in_cache != 0) { + while (FIFO_IS_FULL()) { + cpu_relax(); + } + __raw_writel(head_p->val,(void __iomem *)head_p->reg); + __p_add(&head_p, HEAD_ADD); + } + __adi_fifo_drain(); + } + + return 0; +} + +int sci_adi_write_fast(unsigned long reg, u16 val, u32 sync) +{ + if (!__adi_addr_check(reg)) { + unsigned long flags; + __arch_default_lock(HWLOCK_ADI, &flags); + __adi_write(reg, val, sync); + __arch_default_unlock(HWLOCK_ADI, &flags); + } + return 0; +} +EXPORT_SYMBOL(sci_adi_write_fast); + +int sci_adi_write(unsigned long reg, u16 or_val, u16 clear_msk) +{ + if (!__adi_addr_check(reg)) { + unsigned long flags; + int ret = 0, val = 0; + __arch_default_lock(HWLOCK_ADI, &flags); + ret = __adi_read(__adi_translate_addr(reg), &val); + if (!ret) + __adi_write(reg, (val & ~clear_msk) | or_val, 1); + __arch_default_unlock(HWLOCK_ADI, &flags); + if (ret) + BUG_ON(1); + } + return 0; +} +EXPORT_SYMBOL(sci_adi_write); + +static void __init __adi_init(void) +{ + uint32_t value; + value = __raw_readl((void __iomem *)REG_ADI_CTRL0); + + if (__adi_ver() == 0) { + value &= ~BIT_ARM_SCLK_EN; + value |= BITS_CMMB_WR_PRI; + __raw_writel(value, (void __iomem *)REG_ADI_CTRL0); + +#ifdef CONFIG_ARCH_SCX35LT8 + value = __raw_readl((void __iomem *)REG_ADI_CHNL_PRIL); +#else + value = __raw_readl((void __iomem *)REG_ADI_CHNL_PRI); +#endif + value |= BITS_PD_WR_PRI | BITS_RFT_WR_PRI | + BITS_DSP_RD_PRI | BITS_DSP_WR_PRI | + BITS_ARM_RD_PRI | BITS_ARM_WR_PRI | + BITS_STC_WR_PRI | BITS_INT_STEAL_PRI; +#ifdef CONFIG_ARCH_SCX35LT8 + __raw_writel(value, (void __iomem *)REG_ADI_CHNL_PRIL); +#else + __raw_writel(value, (void __iomem *)REG_ADI_CHNL_PRI); +#endif + readback_addr_mak = 0x7ff; + } else if (__adi_ver() == 1) { + if (value) + WARN_ON(1); + + value = VALUE_CH_PRI; +#ifdef CONFIG_ARCH_SCX35LT8 + __raw_writel(value, (void __iomem *)REG_ADI_CHNL_PRIL); + __raw_writel(value, (void __iomem *)REG_ADI_CHNL_PRIH); +#else + __raw_writel(value, (void __iomem *)REG_ADI_CHNL_PRI); +#endif + value = __raw_readl((void __iomem *)REG_ADI_GSSI_CFG0); + readback_addr_mak = (value & 0x3f) - ((value >> 11) & 0x1f) - 1; + readback_addr_mak = (1<<(readback_addr_mak + 2)) - 1; + } +} + +int __init sci_adi_init(void) +{ + struct resource res; + struct device_node *np; + + + np = of_find_compatible_node(NULL, NULL, "sprd,adi"); + if (of_can_translate_address(np)) { + of_address_to_resource(np, 0, &res); + sprd_adi_phys = res.start; + sprd_adi_base = ioremap_nocache(res.start, + resource_size(&res)); + if (!sprd_adi_base) { + printk("%s: no such memory!\n", __func__); + return -ENOMEM; + } + printk("%s: sprd_adi_base=%lx\n", __func__, sprd_adi_base); + } + +#if defined(CONFIG_ARCH_SC8825) + /* enable adi in global regs */ + sci_glb_set((unsigned long )REG_GLB_GEN0, BIT_ADI_EB); + /* reset adi */ + sci_glb_set((unsigned long )REG_GLB_SOFT_RST, BIT_ADI_RST); + udelay(2); + sci_glb_clr((unsigned long )REG_GLB_SOFT_RST, BIT_ADI_RST); + +#elif defined(CONFIG_ARCH_SCX35) + /*enable adi in global regs*/ + sci_glb_set((unsigned long )REG_AON_APB_APB_EB0, BIT_ADI_EB); +#endif + __adi_init(); + + return 0; +} + |
