diff options
Diffstat (limited to 'drivers')
86 files changed, 7644 insertions, 880 deletions
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 3143db57ce44..b0cf97d01fbb 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -24,6 +24,12 @@ config DEVKMEM kind of kernel debugging operations. When in doubt, say "N". +config VF610_SEMA4 + bool "VF610 SEMA4 driver" + depends on SOC_VF610 + help + Support for VF610 SEMA4 driver, most people should say N here. + config SGI_SNSC bool "SGI Altix system controller communication support" depends on (IA64_SGI_SN2 || IA64_GENERIC) diff --git a/drivers/char/Makefile b/drivers/char/Makefile index d8a7579300d2..fef56bfad9db 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -59,4 +59,5 @@ obj-$(CONFIG_JS_RTC) += js-rtc.o js-rtc-y = rtc.o obj-$(CONFIG_TILE_SROM) += tile-srom.o +obj-$(CONFIG_VF610_SEMA4) += vf610_sema4.o obj-$(CONFIG_XILLYBUS) += xillybus/ diff --git a/drivers/char/vf610_sema4.c b/drivers/char/vf610_sema4.c new file mode 100644 index 000000000000..dff78bd3e22b --- /dev/null +++ b/drivers/char/vf610_sema4.c @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Copyright (C) 2016 Toradex AG. + * + * Taken from the 4.1.15 kernel release for i.MX7 by Freescale. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/vf610_sema4.h> + +static struct vf610_sema4_mutex_device *vf610_sema4; + +struct vf610_sema4_mutex * +vf610_sema4_mutex_create(u32 dev_num, u32 mutex_num) +{ + struct vf610_sema4_mutex *mutex_ptr = NULL; + + if (!vf610_sema4) + return ERR_PTR(-ENODEV); + + if (mutex_num >= SEMA4_NUM_GATES || dev_num >= SEMA4_NUM_DEVICES) + return ERR_PTR(-EINVAL); + + if (vf610_sema4->cpine_val & (1 < mutex_num)) { + pr_err("Error: requiring a allocated sema4.\n"); + pr_err("mutex_num %d cpine_val 0x%08x.\n", + mutex_num, vf610_sema4->cpine_val); + } + + mutex_ptr = kzalloc(sizeof(*mutex_ptr), GFP_KERNEL); + if (!mutex_ptr) + return ERR_PTR(-ENOMEM); + + vf610_sema4->mutex_ptr[mutex_num] = mutex_ptr; + vf610_sema4->alloced |= 1 < mutex_num; + vf610_sema4->cpine_val |= idx_sema4[mutex_num]; + writew(vf610_sema4->cpine_val, vf610_sema4->ioaddr + SEMA4_CP0INE); + + mutex_ptr->valid = CORE_MUTEX_VALID; + mutex_ptr->gate_num = mutex_num; + init_waitqueue_head(&mutex_ptr->wait_q); + + return mutex_ptr; +} +EXPORT_SYMBOL(vf610_sema4_mutex_create); + +int vf610_sema4_mutex_destroy(struct vf610_sema4_mutex *mutex_ptr) +{ + u32 mutex_num; + + if ((mutex_ptr == NULL) || (mutex_ptr->valid != CORE_MUTEX_VALID)) + return -EINVAL; + + mutex_num = mutex_ptr->gate_num; + if ((vf610_sema4->cpine_val & idx_sema4[mutex_num]) == 0) { + pr_err("Error: trying to destroy a un-allocated sema4.\n"); + pr_err("mutex_num %d cpine_val 0x%08x.\n", + mutex_num, vf610_sema4->cpine_val); + } + vf610_sema4->mutex_ptr[mutex_num] = NULL; + vf610_sema4->alloced &= ~(1 << mutex_num); + vf610_sema4->cpine_val &= ~(idx_sema4[mutex_num]); + writew(vf610_sema4->cpine_val, vf610_sema4->ioaddr + SEMA4_CP0INE); + + kfree(mutex_ptr); + + return 0; +} +EXPORT_SYMBOL(vf610_sema4_mutex_destroy); + +int _vf610_sema4_mutex_lock(struct vf610_sema4_mutex *mutex_ptr) +{ + int ret = 0, i = 0; + + if ((mutex_ptr == NULL) || (mutex_ptr->valid != CORE_MUTEX_VALID)) + return -EINVAL; + + i = mutex_ptr->gate_num; + mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i); + mutex_ptr->gate_val &= SEMA4_GATE_MASK; + /* Check to see if this core already own it */ + if (mutex_ptr->gate_val == SEMA4_A5_LOCK) { + /* return -EBUSY, invoker should be in sleep, and re-lock ag */ + pr_err("%s -> %s %d already locked, wait! num %d val %d.\n", + __FILE__, __func__, __LINE__, + i, mutex_ptr->gate_val); + ret = -EBUSY; + goto out; + } else { + /* try to lock the mutex */ + mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i); + mutex_ptr->gate_val &= (~SEMA4_GATE_MASK); + mutex_ptr->gate_val |= SEMA4_A5_LOCK; + writeb(mutex_ptr->gate_val, vf610_sema4->ioaddr + i); + mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i); + mutex_ptr->gate_val &= SEMA4_GATE_MASK; + /* double check the mutex is locked, otherwise, return -EBUSY */ + if (mutex_ptr->gate_val != SEMA4_A5_LOCK) { + pr_debug("wait-locked num %d val %d.\n", + i, mutex_ptr->gate_val); + ret = -EBUSY; + } + } +out: + return ret; +} + +int vf610_sema4_mutex_trylock(struct vf610_sema4_mutex *mutex_ptr) +{ + int ret = 0; + + ret = _vf610_sema4_mutex_lock(mutex_ptr); + if (ret == 0) + return SEMA4_A5_LOCK; + else + return ret; +} +EXPORT_SYMBOL(vf610_sema4_mutex_trylock); + +int vf610_sema4_mutex_lock(struct vf610_sema4_mutex *mutex_ptr) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&vf610_sema4->lock, flags); + ret = _vf610_sema4_mutex_lock(mutex_ptr); + spin_unlock_irqrestore(&vf610_sema4->lock, flags); + while (-EBUSY == ret) { + spin_lock_irqsave(&vf610_sema4->lock, flags); + ret = _vf610_sema4_mutex_lock(mutex_ptr); + spin_unlock_irqrestore(&vf610_sema4->lock, flags); + if (ret == 0) + break; + } + + return ret; +} +EXPORT_SYMBOL(vf610_sema4_mutex_lock); + +int vf610_sema4_mutex_unlock(struct vf610_sema4_mutex *mutex_ptr) +{ + int ret = 0, i = 0; + + if ((mutex_ptr == NULL) || (mutex_ptr->valid != CORE_MUTEX_VALID)) + return -EINVAL; + + i = mutex_ptr->gate_num; + mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i); + mutex_ptr->gate_val &= SEMA4_GATE_MASK; + /* make sure it is locked by this core */ + if (mutex_ptr->gate_val != SEMA4_A5_LOCK) { + pr_err("%d Trying to unlock an unlock mutex.\n", __LINE__); + ret = -EINVAL; + goto out; + } + /* unlock it */ + mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i); + mutex_ptr->gate_val &= (~SEMA4_GATE_MASK); + writeb(mutex_ptr->gate_val | SEMA4_UNLOCK, vf610_sema4->ioaddr + i); + mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i); + mutex_ptr->gate_val &= SEMA4_GATE_MASK; + /* make sure it is locked by this core */ + if (mutex_ptr->gate_val == SEMA4_A5_LOCK) + pr_err("%d ERROR, failed to unlock the mutex.\n", __LINE__); + +out: + return ret; +} +EXPORT_SYMBOL(vf610_sema4_mutex_unlock); + +static irqreturn_t vf610_sema4_isr(int irq, void *dev_id) +{ + int i; + struct vf610_sema4_mutex *mutex_ptr; + unsigned int mask; + struct vf610_sema4_mutex_device *vf610_sema4 = dev_id; + + vf610_sema4->cpntf_val = readw(vf610_sema4->ioaddr + SEMA4_CP0NTF); + for (i = 0; i < SEMA4_NUM_GATES; i++) { + mask = idx_sema4[i]; + if ((vf610_sema4->cpntf_val) & mask) { + mutex_ptr = vf610_sema4->mutex_ptr[i]; + /* + * An interrupt is pending on this mutex, the only way + * to clear it is to lock it (either by this core or + * another). + */ + mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i); + mutex_ptr->gate_val &= (~SEMA4_GATE_MASK); + mutex_ptr->gate_val |= SEMA4_A5_LOCK; + writeb(mutex_ptr->gate_val, vf610_sema4->ioaddr + i); + mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i); + mutex_ptr->gate_val &= SEMA4_GATE_MASK; + if (mutex_ptr->gate_val == SEMA4_A5_LOCK) { + /* + * wake up the wait queue, whatever there + * are wait task or not. + * NOTE: check gate is locted or not in + * sema4_lock func by wait task. + */ + mutex_ptr->gate_val = + readb(vf610_sema4->ioaddr + i); + mutex_ptr->gate_val &= (~SEMA4_GATE_MASK); + mutex_ptr->gate_val |= SEMA4_UNLOCK; + + writeb(mutex_ptr->gate_val, + vf610_sema4->ioaddr + i); + wake_up(&mutex_ptr->wait_q); + } else { + pr_debug("can't lock gate%d %s retry!\n", i, + mutex_ptr->gate_val ? + "locked by m4" : ""); + } + } + } + + return IRQ_HANDLED; +} + +static const struct of_device_id vf610_sema4_dt_ids[] = { + { .compatible = "fsl,vf610-sema4", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vf610_sema4_dt_ids); + +static int vf610_sema4_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret; + + vf610_sema4 = devm_kzalloc(&pdev->dev, sizeof(*vf610_sema4), GFP_KERNEL); + if (!vf610_sema4) + return -ENOMEM; + + vf610_sema4->dev = &pdev->dev; + vf610_sema4->cpine_val = 0; + spin_lock_init(&vf610_sema4->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (IS_ERR(res)) { + dev_err(&pdev->dev, "unable to get vf610 sema4 resource 0\n"); + ret = -ENODEV; + goto err; + } + + vf610_sema4->ioaddr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(vf610_sema4->ioaddr)) { + ret = PTR_ERR(vf610_sema4->ioaddr); + goto err; + } + + vf610_sema4->irq = platform_get_irq(pdev, 0); + if (!vf610_sema4->irq) { + dev_err(&pdev->dev, "failed to get irq\n"); + ret = -ENODEV; + goto err; + } + + ret = devm_request_irq(&pdev->dev, vf610_sema4->irq, vf610_sema4_isr, + IRQF_SHARED, "vf610-sema4", vf610_sema4); + if (ret) { + dev_err(&pdev->dev, "failed to request vf610 sema4 irq\n"); + ret = -ENODEV; + goto err; + } + + platform_set_drvdata(pdev, vf610_sema4); + +err: + return ret; +} + +static int vf610_sema4_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver vf610_sema4_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "vf610-sema4", + .of_match_table = vf610_sema4_dt_ids, + }, + .probe = vf610_sema4_probe, + .remove = vf610_sema4_remove, +}; + +module_platform_driver(vf610_sema4_driver); + +MODULE_DESCRIPTION("VF610 SEMA4 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/imx/clk-gate2.c b/drivers/clk/imx/clk-gate2.c index 8935bff99fe7..db44a198a0d9 100644 --- a/drivers/clk/imx/clk-gate2.c +++ b/drivers/clk/imx/clk-gate2.c @@ -31,6 +31,7 @@ struct clk_gate2 { struct clk_hw hw; void __iomem *reg; u8 bit_idx; + u8 cgr_val; u8 flags; spinlock_t *lock; unsigned int *share_count; @@ -50,7 +51,8 @@ static int clk_gate2_enable(struct clk_hw *hw) goto out; reg = readl(gate->reg); - reg |= 3 << gate->bit_idx; + reg &= ~(3 << gate->bit_idx); + reg |= gate->cgr_val << gate->bit_idx; writel(reg, gate->reg); out: @@ -125,7 +127,7 @@ static struct clk_ops clk_gate2_ops = { struct clk *clk_register_gate2(struct device *dev, const char *name, const char *parent_name, unsigned long flags, - void __iomem *reg, u8 bit_idx, + void __iomem *reg, u8 bit_idx, u8 cgr_val, u8 clk_gate2_flags, spinlock_t *lock, unsigned int *share_count) { @@ -140,6 +142,7 @@ struct clk *clk_register_gate2(struct device *dev, const char *name, /* struct clk_gate2 assignments */ gate->reg = reg; gate->bit_idx = bit_idx; + gate->cgr_val = cgr_val; gate->flags = clk_gate2_flags; gate->lock = lock; gate->share_count = share_count; diff --git a/drivers/clk/imx/clk-vf610.c b/drivers/clk/imx/clk-vf610.c index 0a94d9661d91..8323efc46997 100644 --- a/drivers/clk/imx/clk-vf610.c +++ b/drivers/clk/imx/clk-vf610.c @@ -10,6 +10,7 @@ #include <linux/of_address.h> #include <linux/clk.h> +#include <linux/syscore_ops.h> #include <dt-bindings/clock/vf610-clock.h> #include "clk.h" @@ -40,6 +41,7 @@ #define CCM_CCGR9 (ccm_base + 0x64) #define CCM_CCGR10 (ccm_base + 0x68) #define CCM_CCGR11 (ccm_base + 0x6c) +#define CCM_CCGRx(x) (CCM_CCGR0 + (x) * 4) #define CCM_CMEOR0 (ccm_base + 0x70) #define CCM_CMEOR1 (ccm_base + 0x74) #define CCM_CMEOR2 (ccm_base + 0x78) @@ -115,10 +117,25 @@ static struct clk_div_table pll4_audio_div_table[] = { static struct clk *clk[VF610_CLK_END]; static struct clk_onecell_data clk_data; +static u32 anadig_pll3_ctrl; +static u32 anadig_pll4_ctrl; +static u32 anadig_pll5_ctrl; +static u32 anadig_pll6_ctrl; +static u32 anadig_pll7_ctrl; +static u32 ccpgr0; +static u32 cscmr1; +static u32 cscmr2; +static u32 cscdr1; +static u32 cscdr2; +static u32 cscdr3; +static u32 ccgr[12]; + static unsigned int const clks_init_on[] __initconst = { VF610_CLK_SYS_BUS, VF610_CLK_DDR_SEL, VF610_CLK_DAP, + VF610_CLK_DDRMC, + VF610_CLK_WKPU, }; static struct clk * __init vf610_get_fixed_clock( @@ -132,6 +149,57 @@ static struct clk * __init vf610_get_fixed_clock( return clk; }; +static int vf610_clk_suspend(void) +{ + int i; + + anadig_pll3_ctrl = readl_relaxed(PLL3_CTRL); + anadig_pll4_ctrl = readl_relaxed(PLL4_CTRL); + anadig_pll5_ctrl = readl_relaxed(PLL5_CTRL); + anadig_pll6_ctrl = readl_relaxed(PLL6_CTRL); + anadig_pll7_ctrl = readl_relaxed(PLL7_CTRL); + + ccpgr0 = readl_relaxed(CCM_CCPGR0); + cscmr1 = readl_relaxed(CCM_CSCMR1); + cscmr2 = readl_relaxed(CCM_CSCMR2); + + cscdr1 = readl_relaxed(CCM_CSCDR1); + cscdr2 = readl_relaxed(CCM_CSCDR2); + cscdr3 = readl_relaxed(CCM_CSCDR3); + + for (i = 0; i < 12; i++) + ccgr[i] = readl_relaxed(CCM_CCGRx(i)); + + return 0; +} + +static void vf610_clk_resume(void) +{ + int i; + + writel_relaxed(anadig_pll3_ctrl, PLL3_CTRL); + writel_relaxed(anadig_pll4_ctrl, PLL4_CTRL); + writel_relaxed(anadig_pll5_ctrl, PLL5_CTRL); + writel_relaxed(anadig_pll6_ctrl, PLL6_CTRL); + writel_relaxed(anadig_pll7_ctrl, PLL7_CTRL); + + writel_relaxed(ccpgr0, CCM_CCPGR0); + writel_relaxed(cscmr1, CCM_CSCMR1); + writel_relaxed(cscmr2, CCM_CSCMR2); + + writel_relaxed(cscdr1, CCM_CSCDR1); + writel_relaxed(cscdr2, CCM_CSCDR2); + writel_relaxed(cscdr3, CCM_CSCDR3); + + for (i = 0; i < 12; i++) + writel_relaxed(ccgr[i], CCM_CCGRx(i)); +} + +static struct syscore_ops vf610_clk_syscore_ops = { + .suspend = vf610_clk_suspend, + .resume = vf610_clk_resume, +}; + static void __init vf610_clocks_init(struct device_node *ccm_node) { struct device_node *np; @@ -233,6 +301,9 @@ static void __init vf610_clocks_init(struct device_node *ccm_node) clk[VF610_CLK_PLL4_MAIN_DIV] = clk_register_divider_table(NULL, "pll4_audio_div", "pll4_audio", 0, CCM_CACRR, 6, 3, 0, pll4_audio_div_table, &imx_ccm_lock); clk[VF610_CLK_PLL6_MAIN_DIV] = imx_clk_divider("pll6_video_div", "pll6_video", CCM_CACRR, 21, 1); + clk[VF610_CLK_DDRMC] = imx_clk_gate2_cgr("ddrmc", "ddr_sel", CCM_CCGR6, CCM_CCGRx_CGn(14), 0x2); + clk[VF610_CLK_WKPU] = imx_clk_gate2_cgr("wkpu", "ipg_bus", CCM_CCGR4, CCM_CCGRx_CGn(10), 0x2); + clk[VF610_CLK_USBPHY0] = imx_clk_gate("usbphy0", "pll3_usb_otg", PLL3_CTRL, 6); clk[VF610_CLK_USBPHY1] = imx_clk_gate("usbphy1", "pll7_usb_host", PLL7_CTRL, 6); @@ -261,15 +332,16 @@ static void __init vf610_clocks_init(struct device_node *ccm_node) clk[VF610_CLK_ENET_TS] = imx_clk_gate("enet_ts", "enet_ts_sel", CCM_CSCDR1, 23); clk[VF610_CLK_ENET0] = imx_clk_gate2("enet0", "ipg_bus", CCM_CCGR9, CCM_CCGRx_CGn(0)); clk[VF610_CLK_ENET1] = imx_clk_gate2("enet1", "ipg_bus", CCM_CCGR9, CCM_CCGRx_CGn(1)); + clk[VF610_CLK_ESW] = imx_clk_gate2("esw", "ipg_bus", CCM_CCGR10, CCM_CCGRx_CGn(8)); clk[VF610_CLK_PIT] = imx_clk_gate2("pit", "ipg_bus", CCM_CCGR1, CCM_CCGRx_CGn(7)); - clk[VF610_CLK_UART0] = imx_clk_gate2("uart0", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(7)); - clk[VF610_CLK_UART1] = imx_clk_gate2("uart1", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(8)); - clk[VF610_CLK_UART2] = imx_clk_gate2("uart2", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(9)); - clk[VF610_CLK_UART3] = imx_clk_gate2("uart3", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(10)); - clk[VF610_CLK_UART4] = imx_clk_gate2("uart4", "ipg_bus", CCM_CCGR6, CCM_CCGRx_CGn(9)); - clk[VF610_CLK_UART5] = imx_clk_gate2("uart5", "ipg_bus", CCM_CCGR6, CCM_CCGRx_CGn(10)); + clk[VF610_CLK_UART0] = imx_clk_gate2_cgr("uart0", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(7), 0x2); + clk[VF610_CLK_UART1] = imx_clk_gate2_cgr("uart1", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(8), 0x2); + clk[VF610_CLK_UART2] = imx_clk_gate2_cgr("uart2", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(9), 0x2); + clk[VF610_CLK_UART3] = imx_clk_gate2_cgr("uart3", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(10), 0x2); + clk[VF610_CLK_UART4] = imx_clk_gate2_cgr("uart4", "ipg_bus", CCM_CCGR6, CCM_CCGRx_CGn(9), 0x2); + clk[VF610_CLK_UART5] = imx_clk_gate2_cgr("uart5", "ipg_bus", CCM_CCGR6, CCM_CCGRx_CGn(10), 0x2); clk[VF610_CLK_I2C0] = imx_clk_gate2("i2c0", "ipg_bus", CCM_CCGR4, CCM_CCGRx_CGn(6)); clk[VF610_CLK_I2C1] = imx_clk_gate2("i2c1", "ipg_bus", CCM_CCGR4, CCM_CCGRx_CGn(7)); @@ -321,11 +393,14 @@ static void __init vf610_clocks_init(struct device_node *ccm_node) clk[VF610_CLK_DCU0_SEL] = imx_clk_mux("dcu0_sel", CCM_CSCMR1, 28, 1, dcu_sels, 2); clk[VF610_CLK_DCU0_EN] = imx_clk_gate("dcu0_en", "dcu0_sel", CCM_CSCDR3, 19); clk[VF610_CLK_DCU0_DIV] = imx_clk_divider("dcu0_div", "dcu0_en", CCM_CSCDR3, 16, 3); - clk[VF610_CLK_DCU0] = imx_clk_gate2("dcu0", "dcu0_div", CCM_CCGR3, CCM_CCGRx_CGn(8)); + clk[VF610_CLK_DCU0] = imx_clk_gate2("dcu0", "ipg_bus", CCM_CCGR3, CCM_CCGRx_CGn(8)); clk[VF610_CLK_DCU1_SEL] = imx_clk_mux("dcu1_sel", CCM_CSCMR1, 29, 1, dcu_sels, 2); clk[VF610_CLK_DCU1_EN] = imx_clk_gate("dcu1_en", "dcu1_sel", CCM_CSCDR3, 23); clk[VF610_CLK_DCU1_DIV] = imx_clk_divider("dcu1_div", "dcu1_en", CCM_CSCDR3, 20, 3); - clk[VF610_CLK_DCU1] = imx_clk_gate2("dcu1", "dcu1_div", CCM_CCGR9, CCM_CCGRx_CGn(8)); + clk[VF610_CLK_DCU1] = imx_clk_gate2("dcu1", "ipg_bus", CCM_CCGR9, CCM_CCGRx_CGn(8)); + + clk[VF610_CLK_TCON0] = imx_clk_gate2("tcon0", "platform_bus", CCM_CCGR1, CCM_CCGRx_CGn(13)); + clk[VF610_CLK_TCON1] = imx_clk_gate2("tcon1", "platform_bus", CCM_CCGR7, CCM_CCGRx_CGn(13)); clk[VF610_CLK_ESAI_SEL] = imx_clk_mux("esai_sel", CCM_CSCMR1, 20, 2, esai_sels, 4); clk[VF610_CLK_ESAI_EN] = imx_clk_gate("esai_en", "esai_sel", CCM_CSCDR2, 30); @@ -401,17 +476,21 @@ static void __init vf610_clocks_init(struct device_node *ccm_node) clk_set_rate(clk[VF610_CLK_QSPI1_X2_DIV], clk_get_rate(clk[VF610_CLK_QSPI1_X4_DIV]) / 2); clk_set_rate(clk[VF610_CLK_QSPI1_X1_DIV], clk_get_rate(clk[VF610_CLK_QSPI1_X2_DIV]) / 2); - clk_set_parent(clk[VF610_CLK_SAI0_SEL], clk[VF610_CLK_AUDIO_EXT]); + clk_set_parent(clk[VF610_CLK_SAI0_SEL], clk[VF610_CLK_PLL4_MAIN_DIV]); clk_set_parent(clk[VF610_CLK_SAI1_SEL], clk[VF610_CLK_AUDIO_EXT]); - clk_set_parent(clk[VF610_CLK_SAI2_SEL], clk[VF610_CLK_AUDIO_EXT]); + clk_set_parent(clk[VF610_CLK_SAI2_SEL], clk[VF610_CLK_PLL4_MAIN_DIV]); clk_set_parent(clk[VF610_CLK_SAI3_SEL], clk[VF610_CLK_AUDIO_EXT]); + clk_set_rate(clk[VF610_CLK_PLL4_MAIN_DIV], 147456000); for (i = 0; i < ARRAY_SIZE(clks_init_on); i++) clk_prepare_enable(clk[clks_init_on[i]]); + register_syscore_ops(&vf610_clk_syscore_ops); + /* Add the clocks to provider list */ clk_data.clks = clk; clk_data.clk_num = ARRAY_SIZE(clk); of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data); } CLK_OF_DECLARE(vf610, "fsl,vf610-ccm", vf610_clocks_init); + diff --git a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h index c94ac5c26226..9311755da52f 100644 --- a/drivers/clk/imx/clk.h +++ b/drivers/clk/imx/clk.h @@ -41,7 +41,7 @@ struct clk *imx_clk_pllv3(enum imx_pllv3_type type, const char *name, struct clk *clk_register_gate2(struct device *dev, const char *name, const char *parent_name, unsigned long flags, - void __iomem *reg, u8 bit_idx, + void __iomem *reg, u8 bit_idx, u8 cgr_val, u8 clk_gate_flags, spinlock_t *lock, unsigned int *share_count); @@ -55,7 +55,7 @@ static inline struct clk *imx_clk_gate2(const char *name, const char *parent, void __iomem *reg, u8 shift) { return clk_register_gate2(NULL, name, parent, CLK_SET_RATE_PARENT, reg, - shift, 0, &imx_ccm_lock, NULL); + shift, 0x3, 0, &imx_ccm_lock, NULL); } static inline struct clk *imx_clk_gate2_shared(const char *name, @@ -63,7 +63,14 @@ static inline struct clk *imx_clk_gate2_shared(const char *name, unsigned int *share_count) { return clk_register_gate2(NULL, name, parent, CLK_SET_RATE_PARENT, reg, - shift, 0, &imx_ccm_lock, share_count); + shift, 0x3, 0, &imx_ccm_lock, share_count); +} + +static inline struct clk *imx_clk_gate2_cgr(const char *name, const char *parent, + void __iomem *reg, u8 shift, u8 cgr_val) +{ + return clk_register_gate2(NULL, name, parent, CLK_SET_RATE_PARENT, reg, + shift, cgr_val, 0, &imx_ccm_lock, NULL); } struct clk *imx_clk_pfd(const char *name, const char *parent_name, diff --git a/drivers/clocksource/vf_pit_timer.c b/drivers/clocksource/vf_pit_timer.c index a0e6c68536a1..934fe264e58d 100644 --- a/drivers/clocksource/vf_pit_timer.c +++ b/drivers/clocksource/vf_pit_timer.c @@ -36,6 +36,7 @@ static void __iomem *clksrc_base; static void __iomem *clkevt_base; static unsigned long cycle_per_jiffy; +static void __iomem *timer_base; static inline void pit_timer_enable(void) { @@ -57,12 +58,17 @@ static u64 notrace pit_read_sched_clock(void) return ~__raw_readl(clksrc_base + PITCVAL); } -static int __init pit_clocksource_init(unsigned long rate) +static void pit_load_and_start_clocksource(int clksrc_ldval) { - /* set the max load value and start the clock source counter */ __raw_writel(0, clksrc_base + PITTCTRL); - __raw_writel(~0UL, clksrc_base + PITLDVAL); + __raw_writel(clksrc_ldval, clksrc_base + PITLDVAL); __raw_writel(PITTCTRL_TEN, clksrc_base + PITTCTRL); +} + +static int __init pit_clocksource_init(unsigned long rate) +{ + /* set the max load value and start the clock source counter */ + pit_load_and_start_clocksource(~0UL); sched_clock_register(pit_read_sched_clock, 32, rate); return clocksource_mmio_init(clksrc_base + PITCVAL, "vf-pit", rate, @@ -118,6 +124,21 @@ static irqreturn_t pit_timer_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } +static unsigned int src_ldval; + +static void pit_resume(struct clock_event_device *evt) +{ + /* Enable the PIT module on resume */ + __raw_writel(~PITMCR_MDIS, timer_base + PITMCR); + + pit_load_and_start_clocksource(src_ldval); +} + +static void pit_suspend(struct clock_event_device *evt) +{ + src_ldval = __raw_readl(clksrc_base + PITLDVAL); +} + static struct clock_event_device clockevent_pit = { .name = "VF pit timer", .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, @@ -125,6 +146,8 @@ static struct clock_event_device clockevent_pit = { .set_state_periodic = pit_set_periodic, .set_next_event = pit_set_next_event, .rating = 300, + .resume = pit_resume, + .suspend = pit_suspend, }; static struct irqaction pit_timer_irq = { @@ -159,7 +182,6 @@ static int __init pit_clockevent_init(unsigned long rate, int irq) static void __init pit_timer_init(struct device_node *np) { struct clk *pit_clk; - void __iomem *timer_base; unsigned long clk_rate; int irq; diff --git a/drivers/dma/fsl-edma.c b/drivers/dma/fsl-edma.c index 915eec3cc279..be2e62b87948 100644 --- a/drivers/dma/fsl-edma.c +++ b/drivers/dma/fsl-edma.c @@ -116,6 +116,10 @@ BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \ BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) | \ BIT(DMA_SLAVE_BUSWIDTH_8_BYTES) +enum fsl_edma_pm_state { + RUNNING = 0, + SUSPENDED, +}; struct fsl_edma_hw_tcd { __le32 saddr; @@ -147,6 +151,9 @@ struct fsl_edma_slave_config { struct fsl_edma_chan { struct virt_dma_chan vchan; enum dma_status status; + enum fsl_edma_pm_state pm_state; + bool idle; + u32 slave_id; struct fsl_edma_engine *edma; struct fsl_edma_desc *edesc; struct fsl_edma_slave_config fsc; @@ -298,6 +305,7 @@ static int fsl_edma_terminate_all(struct dma_chan *chan) spin_lock_irqsave(&fsl_chan->vchan.lock, flags); fsl_edma_disable_request(fsl_chan); fsl_chan->edesc = NULL; + fsl_chan->idle = true; vchan_get_all_descriptors(&fsl_chan->vchan, &head); spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); vchan_dma_desc_free_list(&fsl_chan->vchan, &head); @@ -313,6 +321,7 @@ static int fsl_edma_pause(struct dma_chan *chan) if (fsl_chan->edesc) { fsl_edma_disable_request(fsl_chan); fsl_chan->status = DMA_PAUSED; + fsl_chan->idle = true; } spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); return 0; @@ -327,6 +336,7 @@ static int fsl_edma_resume(struct dma_chan *chan) if (fsl_chan->edesc) { fsl_edma_enable_request(fsl_chan); fsl_chan->status = DMA_IN_PROGRESS; + fsl_chan->idle = false; } spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); return 0; @@ -648,6 +658,7 @@ static void fsl_edma_xfer_desc(struct fsl_edma_chan *fsl_chan) fsl_edma_set_tcd_regs(fsl_chan, fsl_chan->edesc->tcd[0].vtcd); fsl_edma_enable_request(fsl_chan); fsl_chan->status = DMA_IN_PROGRESS; + fsl_chan->idle = false; } static irqreturn_t fsl_edma_tx_handler(int irq, void *dev_id) @@ -676,6 +687,7 @@ static irqreturn_t fsl_edma_tx_handler(int irq, void *dev_id) vchan_cookie_complete(&fsl_chan->edesc->vdesc); fsl_chan->edesc = NULL; fsl_chan->status = DMA_COMPLETE; + fsl_chan->idle = true; } else { vchan_cyclic_callback(&fsl_chan->edesc->vdesc); } @@ -704,6 +716,7 @@ static irqreturn_t fsl_edma_err_handler(int irq, void *dev_id) edma_writeb(fsl_edma, EDMA_CERR_CERR(ch), fsl_edma->membase + EDMA_CERR); fsl_edma->chans[ch].status = DMA_ERROR; + fsl_edma->chans[ch].idle = true; } } return IRQ_HANDLED; @@ -724,6 +737,12 @@ static void fsl_edma_issue_pending(struct dma_chan *chan) spin_lock_irqsave(&fsl_chan->vchan.lock, flags); + if (unlikely(fsl_chan->pm_state != RUNNING)) { + spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); + /* cannot submit due to suspend */ + return; + } + if (vchan_issue_pending(&fsl_chan->vchan) && !fsl_chan->edesc) fsl_edma_xfer_desc(fsl_chan); @@ -735,6 +754,7 @@ static struct dma_chan *fsl_edma_xlate(struct of_phandle_args *dma_spec, { struct fsl_edma_engine *fsl_edma = ofdma->of_dma_data; struct dma_chan *chan, *_chan; + struct fsl_edma_chan *fsl_chan; unsigned long chans_per_mux = fsl_edma->n_chans / DMAMUX_NR; if (dma_spec->args_count != 2) @@ -748,8 +768,10 @@ static struct dma_chan *fsl_edma_xlate(struct of_phandle_args *dma_spec, chan = dma_get_slave_channel(chan); if (chan) { chan->device->privatecnt++; - fsl_edma_chan_mux(to_fsl_edma_chan(chan), - dma_spec->args[1], true); + fsl_chan = to_fsl_edma_chan(chan); + fsl_chan->slave_id = dma_spec->args[1]; + fsl_edma_chan_mux(fsl_chan, fsl_chan->slave_id, + true); mutex_unlock(&fsl_edma->fsl_edma_mutex); return chan; } @@ -888,7 +910,9 @@ static int fsl_edma_probe(struct platform_device *pdev) struct fsl_edma_chan *fsl_chan = &fsl_edma->chans[i]; fsl_chan->edma = fsl_edma; - + fsl_chan->pm_state = RUNNING; + fsl_chan->slave_id = 0; + fsl_chan->idle = true; fsl_chan->vchan.desc_free = fsl_edma_free_desc; vchan_init(&fsl_chan->vchan, &fsl_edma->dma_dev); @@ -959,6 +983,60 @@ static int fsl_edma_remove(struct platform_device *pdev) return 0; } +static int fsl_edma_suspend_late(struct device *dev) +{ + struct fsl_edma_engine *fsl_edma = dev_get_drvdata(dev); + struct fsl_edma_chan *fsl_chan; + unsigned long flags; + int i; + + for (i = 0; i < fsl_edma->n_chans; i++) { + fsl_chan = &fsl_edma->chans[i]; + spin_lock_irqsave(&fsl_chan->vchan.lock, flags); + /* Make sure chan is idle or will force disable. */ + if (unlikely(!fsl_chan->idle)) { + dev_warn(dev, "WARN: There is non-idle channel."); + fsl_edma_disable_request(fsl_chan); + fsl_edma_chan_mux(fsl_chan, 0, false); + } + + fsl_chan->pm_state = SUSPENDED; + spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); + } + + return 0; +} + +static int fsl_edma_resume_early(struct device *dev) +{ + struct fsl_edma_engine *fsl_edma = dev_get_drvdata(dev); + struct fsl_edma_chan *fsl_chan; + int i; + + for (i = 0; i < fsl_edma->n_chans; i++) { + fsl_chan = &fsl_edma->chans[i]; + fsl_chan->pm_state = RUNNING; + edma_writew(fsl_edma, 0x0, fsl_edma->membase + EDMA_TCD_CSR(i)); + if (fsl_chan->slave_id != 0) + fsl_edma_chan_mux(fsl_chan, fsl_chan->slave_id, true); + } + + edma_writel(fsl_edma, EDMA_CR_ERGA | EDMA_CR_ERCA, + fsl_edma->membase + EDMA_CR); + + return 0; +} + +/* + * eDMA provides the service to others, so it should be suspend late + * and resume early. When eDMA suspend, all of the clients should stop + * the DMA data transmission and let the channel idle. + */ +static const struct dev_pm_ops fsl_edma_pm_ops = { + .suspend_late = fsl_edma_suspend_late, + .resume_early = fsl_edma_resume_early, +}; + static const struct of_device_id fsl_edma_dt_ids[] = { { .compatible = "fsl,vf610-edma", }, { /* sentinel */ } @@ -969,6 +1047,7 @@ static struct platform_driver fsl_edma_driver = { .driver = { .name = "fsl-edma", .of_match_table = fsl_edma_dt_ids, + .pm = &fsl_edma_pm_ops, }, .probe = fsl_edma_probe, .remove = fsl_edma_remove, diff --git a/drivers/gpio/gpio-vf610.c b/drivers/gpio/gpio-vf610.c index db95c4b99a74..90fd4bee4821 100644 --- a/drivers/gpio/gpio-vf610.c +++ b/drivers/gpio/gpio-vf610.c @@ -36,7 +36,10 @@ struct vf610_gpio_port { void __iomem *base; void __iomem *gpio_base; u8 irqc[VF610_GPIO_PER_PORT]; + struct platform_device *pdev_wkpu; + s8 fsl_wakeup[VF610_GPIO_PER_PORT]; int irq; + u32 state; }; #define GPIO_PDOR 0x00 @@ -60,6 +63,14 @@ struct vf610_gpio_port { #define PORT_INT_EITHER_EDGE 0xb #define PORT_INT_LOGIC_ONE 0xc +#define WKPU_WISR 0x14 +#define WKPU_IRER 0x18 +#define WKPU_WRER 0x1c +#define WKPU_WIREER 0x28 +#define WKPU_WIFEER 0x2c +#define WKPU_WIFER 0x30 +#define WKPU_WIPUER 0x34 + static struct irq_chip vf610_gpio_irq_chip; static struct vf610_gpio_port *to_vf610_gp(struct gpio_chip *gc) @@ -72,6 +83,11 @@ static const struct of_device_id vf610_gpio_dt_ids[] = { { /* sentinel */ } }; +static const struct of_device_id vf610_wkpu_dt_ids[] = { + { .compatible = "fsl,vf610-wkpu" }, + { /* sentinel */ } +}; + static inline void vf610_gpio_writel(u32 val, void __iomem *reg) { writel_relaxed(val, reg); @@ -147,8 +163,26 @@ static int vf610_gpio_irq_set_type(struct irq_data *d, u32 type) { struct vf610_gpio_port *port = to_vf610_gp(irq_data_get_irq_chip_data(d)); + s8 wkpu_gpio = port->fsl_wakeup[d->hwirq]; u8 irqc; + if (wkpu_gpio >= 0) { + void __iomem *base = platform_get_drvdata(port->pdev_wkpu); + u32 wireer, wifeer; + u32 mask = 1 << wkpu_gpio; + + wireer = vf610_gpio_readl(base + WKPU_WIREER) & ~mask; + wifeer = vf610_gpio_readl(base + WKPU_WIFEER) & ~mask; + + if (type & IRQ_TYPE_EDGE_RISING) + wireer |= mask; + if (type & IRQ_TYPE_EDGE_FALLING) + wifeer |= mask; + + vf610_gpio_writel(wireer, base + WKPU_WIREER); + vf610_gpio_writel(wifeer, base + WKPU_WIFEER); + } + switch (type) { case IRQ_TYPE_EDGE_RISING: irqc = PORT_INT_RISING_EDGE; @@ -202,6 +236,29 @@ static int vf610_gpio_irq_set_wake(struct irq_data *d, u32 enable) { struct vf610_gpio_port *port = to_vf610_gp(irq_data_get_irq_chip_data(d)); + s8 wkpu_gpio = port->fsl_wakeup[d->hwirq]; + + if (wkpu_gpio >= 0) { + void __iomem *base = NULL; + u32 wrer, irer; + + base = platform_get_drvdata(port->pdev_wkpu); + + /* WKPU wakeup flag for LPSTOPx modes... */ + wrer = vf610_gpio_readl(base + WKPU_WRER); + irer = vf610_gpio_readl(base + WKPU_IRER); + + if (enable) { + wrer |= 1 << wkpu_gpio; + irer |= 1 << wkpu_gpio; + } else { + wrer &= ~(1 << wkpu_gpio); + irer &= ~(1 << wkpu_gpio); + } + + vf610_gpio_writel(wrer, base + WKPU_WRER); + vf610_gpio_writel(irer, base + WKPU_IRER); + } if (enable) enable_irq_wake(port->irq); @@ -220,6 +277,77 @@ static struct irq_chip vf610_gpio_irq_chip = { .irq_set_wake = vf610_gpio_irq_set_wake, }; +static int __maybe_unused vf610_gpio_suspend(struct device *dev) +{ + struct vf610_gpio_port *port = dev_get_drvdata(dev); + + port->state = vf610_gpio_readl(port->gpio_base + GPIO_PDOR); + + /* + * There is no need to store Port state since we maintain the state + * alread in the irqc array + */ + + return 0; +} + +static int __maybe_unused vf610_gpio_resume(struct device *dev) +{ + struct vf610_gpio_port *port = dev_get_drvdata(dev); + int i; + + vf610_gpio_writel(port->state, port->gpio_base + GPIO_PDOR); + + for (i = 0; i < port->gc.ngpio; i++) { + u32 irqc = port->irqc[i] << PORT_PCR_IRQC_OFFSET; + + vf610_gpio_writel(irqc, port->base + PORT_PCR(i)); + } + + return 0; +} + +static const struct dev_pm_ops vf610_gpio_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(vf610_gpio_suspend, vf610_gpio_resume) +}; + +static int vf610_gpio_wkpu(struct device_node *np, struct vf610_gpio_port *port) +{ + struct platform_device *pdev = NULL; + struct of_phandle_args arg; + int i, ret; + + for (i = 0; i < VF610_GPIO_PER_PORT; i++) + port->fsl_wakeup[i] = -1; + + for (i = 0;;i++) { + int gpioid, wakeupid, cnt; + + ret = of_parse_phandle_with_fixed_args(np, "fsl,gpio-wakeup", + 3, i, &arg); + + if (ret == -ENOENT) + break; + + if (!pdev) + pdev = of_find_device_by_node(arg.np); + of_node_put(arg.np); + if (!pdev) + return -EPROBE_DEFER; + + gpioid = arg.args[0]; + wakeupid = arg.args[1]; + cnt = arg.args[2]; + + while (cnt-- && gpioid < VF610_GPIO_PER_PORT) + port->fsl_wakeup[gpioid++] = wakeupid++; + } + + port->pdev_wkpu = pdev; + + return 0; +} + static int vf610_gpio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -248,6 +376,10 @@ static int vf610_gpio_probe(struct platform_device *pdev) if (port->irq < 0) return port->irq; + ret = vf610_gpio_wkpu(np, port); + if (ret < 0) + return ret; + gc = &port->gc; gc->of_node = np; gc->dev = dev; @@ -282,6 +414,7 @@ static int vf610_gpio_probe(struct platform_device *pdev) } gpiochip_set_chained_irqchip(gc, &vf610_gpio_irq_chip, port->irq, vf610_gpio_irq_handler); + platform_set_drvdata(pdev, port); return 0; } @@ -289,6 +422,7 @@ static int vf610_gpio_probe(struct platform_device *pdev) static struct platform_driver vf610_gpio_driver = { .driver = { .name = "gpio-vf610", + .pm = &vf610_gpio_pm_ops, .of_match_table = vf610_gpio_dt_ids, }, .probe = vf610_gpio_probe, @@ -300,6 +434,60 @@ static int __init gpio_vf610_init(void) } device_initcall(gpio_vf610_init); +static irqreturn_t vf610_wkpu_irq(int irq, void *data) +{ + void __iomem *base = data; + u32 wisr; + + wisr = vf610_gpio_readl(base + WKPU_WISR); + vf610_gpio_writel(wisr, base + WKPU_WISR); + pr_debug("%s, WKPU interrupt received, flags %08x\n", __func__, wisr); + + return 0; +} + +static int vf610_wkpu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *iores; + void __iomem *base; + int irq, err; + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, iores); + + if (IS_ERR(base)) + return PTR_ERR(base); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + err = devm_request_irq(dev, irq, vf610_wkpu_irq, 0, "wkpu-vf610", base); + if (err) { + dev_err(dev, "Error requesting IRQ!\n"); + return err; + } + + platform_set_drvdata(pdev, base); + + return 0; +} + +static struct platform_driver vf610_wkpu_driver = { + .driver = { + .name = "wkpu-vf610", + .of_match_table = vf610_wkpu_dt_ids, + }, + .probe = vf610_wkpu_probe, +}; + +static int __init vf610_wkpu_init(void) +{ + return platform_driver_register(&vf610_wkpu_driver); +} +device_initcall(vf610_wkpu_init); + MODULE_AUTHOR("Stefan Agner <stefan@agner.ch>"); MODULE_DESCRIPTION("Freescale VF610 GPIO"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index 355ad1b97df6..f256bae67aab 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -1194,12 +1194,7 @@ void drm_atomic_legacy_backoff(struct drm_atomic_state *state) retry: drm_modeset_backoff(state->acquire_ctx); - ret = drm_modeset_lock(&state->dev->mode_config.connection_mutex, - state->acquire_ctx); - if (ret) - goto retry; - ret = drm_modeset_lock_all_crtcs(state->dev, - state->acquire_ctx); + ret = drm_modeset_lock_all_ctx(state->dev, state->acquire_ctx); if (ret) goto retry; } diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index ea443fafb934..ad06c20ea07d 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -1814,6 +1814,161 @@ commit: } /** + * drm_atomic_helper_disable_all - disable all currently active outputs + * @dev: DRM device + * @ctx: lock acquisition context + * + * Loops through all connectors, finding those that aren't turned off and then + * turns them off by setting their DPMS mode to OFF and deactivating the CRTC + * that they are connected to. + * + * This is used for example in suspend/resume to disable all currently active + * functions when suspending. + * + * Note that if callers haven't already acquired all modeset locks this might + * return -EDEADLK, which must be handled by calling drm_modeset_backoff(). + * + * Returns: + * 0 on success or a negative error code on failure. + * + * See also: + * drm_atomic_helper_suspend(), drm_atomic_helper_resume() + */ +int drm_atomic_helper_disable_all(struct drm_device *dev, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_atomic_state *state; + struct drm_connector *conn; + int err; + + state = drm_atomic_state_alloc(dev); + if (!state) + return -ENOMEM; + + state->acquire_ctx = ctx; + + drm_for_each_connector(conn, dev) { + struct drm_crtc *crtc = conn->state->crtc; + struct drm_crtc_state *crtc_state; + + if (!crtc || conn->dpms != DRM_MODE_DPMS_ON) + continue; + + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) { + err = PTR_ERR(crtc_state); + goto free; + } + + crtc_state->active = false; + } + + err = drm_atomic_commit(state); + +free: + if (err < 0) + drm_atomic_state_free(state); + + return err; +} +EXPORT_SYMBOL(drm_atomic_helper_disable_all); + +/** + * drm_atomic_helper_suspend - subsystem-level suspend helper + * @dev: DRM device + * + * Duplicates the current atomic state, disables all active outputs and then + * returns a pointer to the original atomic state to the caller. Drivers can + * pass this pointer to the drm_atomic_helper_resume() helper upon resume to + * restore the output configuration that was active at the time the system + * entered suspend. + * + * Note that it is potentially unsafe to use this. The atomic state object + * returned by this function is assumed to be persistent. Drivers must ensure + * that this holds true. Before calling this function, drivers must make sure + * to suspend fbdev emulation so that nothing can be using the device. + * + * Returns: + * A pointer to a copy of the state before suspend on success or an ERR_PTR()- + * encoded error code on failure. Drivers should store the returned atomic + * state object and pass it to the drm_atomic_helper_resume() helper upon + * resume. + * + * See also: + * drm_atomic_helper_duplicate_state(), drm_atomic_helper_disable_all(), + * drm_atomic_helper_resume() + */ +struct drm_atomic_state *drm_atomic_helper_suspend(struct drm_device *dev) +{ + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_state *state; + int err; + + drm_modeset_acquire_init(&ctx, 0); + +retry: + err = drm_modeset_lock_all_ctx(dev, &ctx); + if (err < 0) { + state = ERR_PTR(err); + goto unlock; + } + + state = drm_atomic_helper_duplicate_state(dev, &ctx); + if (IS_ERR(state)) + goto unlock; + + err = drm_atomic_helper_disable_all(dev, &ctx); + if (err < 0) { + drm_atomic_state_free(state); + state = ERR_PTR(err); + goto unlock; + } + +unlock: + if (PTR_ERR(state) == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry; + } + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + return state; +} +EXPORT_SYMBOL(drm_atomic_helper_suspend); + +/** + * drm_atomic_helper_resume - subsystem-level resume helper + * @dev: DRM device + * @state: atomic state to resume to + * + * Calls drm_mode_config_reset() to synchronize hardware and software states, + * grabs all modeset locks and commits the atomic state object. This can be + * used in conjunction with the drm_atomic_helper_suspend() helper to + * implement suspend/resume for drivers that support atomic mode-setting. + * + * Returns: + * 0 on success or a negative error code on failure. + * + * See also: + * drm_atomic_helper_suspend() + */ +int drm_atomic_helper_resume(struct drm_device *dev, + struct drm_atomic_state *state) +{ + struct drm_mode_config *config = &dev->mode_config; + int err; + + drm_mode_config_reset(dev); + drm_modeset_lock_all(dev); + state->acquire_ctx = config->acquire_ctx; + err = drm_atomic_commit(state); + drm_modeset_unlock_all(dev); + + return err; +} +EXPORT_SYMBOL(drm_atomic_helper_resume); + +/** * drm_atomic_helper_crtc_set_property - helper for crtc properties * @crtc: DRM crtc * @property: DRM property @@ -2426,7 +2581,9 @@ EXPORT_SYMBOL(drm_atomic_helper_connector_duplicate_state); * @ctx: lock acquisition context * * Makes a copy of the current atomic state by looping over all objects and - * duplicating their respective states. + * duplicating their respective states. This is used for example by suspend/ + * resume support code to save the state prior to suspend such that it can + * be restored upon resume. * * Note that this treats atomic state as persistent between save and restore. * Drivers must make sure that this is possible and won't result in confusion @@ -2438,6 +2595,9 @@ EXPORT_SYMBOL(drm_atomic_helper_connector_duplicate_state); * Returns: * A pointer to the copy of the atomic state object on success or an * ERR_PTR()-encoded error code on failure. + * + * See also: + * drm_atomic_helper_suspend(), drm_atomic_helper_resume() */ struct drm_atomic_state * drm_atomic_helper_duplicate_state(struct drm_device *dev, diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c index ef534758a02c..08620a25fe06 100644 --- a/drivers/gpu/drm/drm_crtc_helper.c +++ b/drivers/gpu/drm/drm_crtc_helper.c @@ -855,6 +855,12 @@ EXPORT_SYMBOL(drm_helper_mode_fill_fb_struct); * due to slight differences in allocating shared resources when the * configuration is restored in a different order than when userspace set it up) * need to use their own restore logic. + * + * This function is deprecated. New drivers should implement atomic mode- + * setting and use the atomic suspend/resume helpers. + * + * See also: + * drm_atomic_helper_suspend(), drm_atomic_helper_resume() */ void drm_helper_resume_force_mode(struct drm_device *dev) { diff --git a/drivers/gpu/drm/drm_fb_cma_helper.c b/drivers/gpu/drm/drm_fb_cma_helper.c index c19a62561183..a17d74474f1a 100644 --- a/drivers/gpu/drm/drm_fb_cma_helper.c +++ b/drivers/gpu/drm/drm_fb_cma_helper.c @@ -348,9 +348,6 @@ struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev, } - /* disable all the possible outputs/crtcs before entering KMS mode */ - drm_helper_disable_unused_functions(dev); - ret = drm_fb_helper_initial_config(helper, preferred_bpp); if (ret < 0) { dev_err(dev->dev, "Failed to set initial hw configuration.\n"); @@ -369,6 +366,18 @@ err_free: EXPORT_SYMBOL_GPL(drm_fbdev_cma_init); /** + * drm_fbdev_cma_get_helper() - Get drm_fb_helper struct of a CMA framebuffer + * @fbdev_cma: drm_fbdev_cma struct + * + * Returns the assigned drm_fb_helper struct. + */ +struct drm_fb_helper *drm_fbdev_cma_get_helper(struct drm_fbdev_cma *fbdev_cma) +{ + return &fbdev_cma->fb_helper; +} +EXPORT_SYMBOL_GPL(drm_fbdev_cma_get_helper); + +/** * drm_fbdev_cma_fini() - Free drm_fbdev_cma struct * @fbdev_cma: The drm_fbdev_cma struct */ diff --git a/drivers/gpu/drm/drm_modeset_lock.c b/drivers/gpu/drm/drm_modeset_lock.c index c257de351cfa..220eee1c1ef7 100644 --- a/drivers/gpu/drm/drm_modeset_lock.c +++ b/drivers/gpu/drm/drm_modeset_lock.c @@ -57,11 +57,18 @@ /** * drm_modeset_lock_all - take all modeset locks - * @dev: drm device + * @dev: DRM device * * This function takes all modeset locks, suitable where a more fine-grained - * scheme isn't (yet) implemented. Locks must be dropped with - * drm_modeset_unlock_all. + * scheme isn't (yet) implemented. Locks must be dropped by calling the + * drm_modeset_unlock_all() function. + * + * This function is deprecated. It allocates a lock acquisition context and + * stores it in the DRM device's ->mode_config. This facilitate conversion of + * existing code because it removes the need to manually deal with the + * acquisition context, but it is also brittle because the context is global + * and care must be taken not to nest calls. New code should use the + * drm_modeset_lock_all_ctx() function and pass in the context explicitly. */ void drm_modeset_lock_all(struct drm_device *dev) { @@ -78,39 +85,43 @@ void drm_modeset_lock_all(struct drm_device *dev) drm_modeset_acquire_init(ctx, 0); retry: - ret = drm_modeset_lock(&config->connection_mutex, ctx); - if (ret) - goto fail; - ret = drm_modeset_lock_all_crtcs(dev, ctx); - if (ret) - goto fail; + ret = drm_modeset_lock_all_ctx(dev, ctx); + if (ret < 0) { + if (ret == -EDEADLK) { + drm_modeset_backoff(ctx); + goto retry; + } + + drm_modeset_acquire_fini(ctx); + kfree(ctx); + return; + } WARN_ON(config->acquire_ctx); - /* now we hold the locks, so now that it is safe, stash the - * ctx for drm_modeset_unlock_all(): + /* + * We hold the locks now, so it is safe to stash the acquisition + * context for drm_modeset_unlock_all(). */ config->acquire_ctx = ctx; drm_warn_on_modeset_not_all_locked(dev); - - return; - -fail: - if (ret == -EDEADLK) { - drm_modeset_backoff(ctx); - goto retry; - } - - kfree(ctx); } EXPORT_SYMBOL(drm_modeset_lock_all); /** * drm_modeset_unlock_all - drop all modeset locks - * @dev: device + * @dev: DRM device * - * This function drop all modeset locks taken by drm_modeset_lock_all. + * This function drops all modeset locks taken by a previous call to the + * drm_modeset_lock_all() function. + * + * This function is deprecated. It uses the lock acquisition context stored + * in the DRM device's ->mode_config. This facilitates conversion of existing + * code because it removes the need to manually deal with the acquisition + * context, but it is also brittle because the context is global and care must + * be taken not to nest calls. New code should pass the acquisition context + * directly to the drm_modeset_drop_locks() function. */ void drm_modeset_unlock_all(struct drm_device *dev) { @@ -431,14 +442,34 @@ void drm_modeset_unlock(struct drm_modeset_lock *lock) } EXPORT_SYMBOL(drm_modeset_unlock); -/* In some legacy codepaths it's convenient to just grab all the crtc and plane - * related locks. */ -int drm_modeset_lock_all_crtcs(struct drm_device *dev, - struct drm_modeset_acquire_ctx *ctx) +/** + * drm_modeset_lock_all_ctx - take all modeset locks + * @dev: DRM device + * @ctx: lock acquisition context + * + * This function takes all modeset locks, suitable where a more fine-grained + * scheme isn't (yet) implemented. + * + * Unlike drm_modeset_lock_all(), it doesn't take the dev->mode_config.mutex + * since that lock isn't required for modeset state changes. Callers which + * need to grab that lock too need to do so outside of the acquire context + * @ctx. + * + * Locks acquired with this function should be released by calling the + * drm_modeset_drop_locks() function on @ctx. + * + * Returns: 0 on success or a negative error-code on failure. + */ +int drm_modeset_lock_all_ctx(struct drm_device *dev, + struct drm_modeset_acquire_ctx *ctx) { struct drm_crtc *crtc; struct drm_plane *plane; - int ret = 0; + int ret; + + ret = drm_modeset_lock(&dev->mode_config.connection_mutex, ctx); + if (ret) + return ret; drm_for_each_crtc(crtc, dev) { ret = drm_modeset_lock(&crtc->mutex, ctx); @@ -454,4 +485,4 @@ int drm_modeset_lock_all_crtcs(struct drm_device *dev, return 0; } -EXPORT_SYMBOL(drm_modeset_lock_all_crtcs); +EXPORT_SYMBOL(drm_modeset_lock_all_ctx); diff --git a/drivers/gpu/drm/fsl-dcu/Makefile b/drivers/gpu/drm/fsl-dcu/Makefile index 6ea1523ae6ec..b35a292287f3 100644 --- a/drivers/gpu/drm/fsl-dcu/Makefile +++ b/drivers/gpu/drm/fsl-dcu/Makefile @@ -3,5 +3,6 @@ fsl-dcu-drm-y := fsl_dcu_drm_drv.o \ fsl_dcu_drm_rgb.o \ fsl_dcu_drm_plane.o \ fsl_dcu_drm_crtc.o \ - fsl_dcu_drm_fbdev.o + fsl_dcu_drm_fbdev.o \ + fsl_tcon.o obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu-drm.o diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c index 82a3d311e164..9ceeb1a5b47d 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c @@ -17,6 +17,9 @@ #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_flip_work.h> + +#include <video/display_timing.h> #include "fsl_dcu_drm_crtc.h" #include "fsl_dcu_drm_drv.h" @@ -25,11 +28,24 @@ static void fsl_dcu_drm_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_crtc_state *old_crtc_state) { + struct fsl_dcu_drm_crtc *dcu_crtc = to_fsl_dcu_crtc(crtc); + if (crtc->state->event) { + crtc->state->event->pipe = drm_crtc_index(crtc); + + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + dcu_crtc->event = crtc->state->event; + crtc->state->event = NULL; + } } static int fsl_dcu_drm_crtc_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *state) { + struct fsl_dcu_drm_crtc *dcu_crtc = to_fsl_dcu_crtc(crtc); + + if (dcu_crtc->event != NULL && state->event != NULL) + return -EINVAL; + return 0; } @@ -38,38 +54,99 @@ static void fsl_dcu_drm_crtc_atomic_flush(struct drm_crtc *crtc, { } +void fsl_dcu_crtc_finish_page_flip(struct drm_device *dev) +{ + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + struct fsl_dcu_drm_crtc *dcu_crtc = &fsl_dev->crtc; + struct drm_crtc *crtc = &dcu_crtc->base; + unsigned long flags; + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + if (dcu_crtc->event) { + drm_send_vblank_event(crtc->dev, + drm_crtc_index(crtc), + dcu_crtc->event); + drm_vblank_put(dev, drm_crtc_index(crtc)); + dcu_crtc->event = NULL; + } + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); +} + +void fsl_dcu_crtc_cancel_page_flip(struct drm_device *dev, struct drm_file *f) +{ + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + struct fsl_dcu_drm_crtc *dcu_crtc = &fsl_dev->crtc; + struct drm_crtc *crtc = &dcu_crtc->base; + struct drm_pending_vblank_event *event; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + event = dcu_crtc->event; + + if (event && event->base.file_priv == f) { + event->base.destroy(&event->base); + drm_vblank_put(dev, drm_crtc_index(crtc)); + dcu_crtc->event = NULL; + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + static void fsl_dcu_drm_disable_crtc(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; - int ret; - - ret = regmap_update_bits(fsl_dev->regmap, DCU_DCU_MODE, - DCU_MODE_DCU_MODE_MASK, - DCU_MODE_DCU_MODE(DCU_MODE_OFF)); - if (ret) - dev_err(fsl_dev->dev, "Disable CRTC failed\n"); - ret = regmap_write(fsl_dev->regmap, DCU_UPDATE_MODE, - DCU_UPDATE_MODE_READREG); - if (ret) - dev_err(fsl_dev->dev, "Enable CRTC failed\n"); + int i; + unsigned int value; + unsigned int mode; + + /* Disable automatic transfer mode */ + regmap_update_bits(fsl_dev->regmap, DCU_UPDATE_MODE, + DCU_UPDATE_MODE_MODE, 0); + + /* Disable all planes */ + for (i = 0; i < fsl_dev->soc->total_layer; i++) { + regmap_read(fsl_dev->regmap, DCU_CTRLDESCLN(i, 4), &value); + value &= ~DCU_LAYER_EN; + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(i, 4), value); + } + regmap_write(fsl_dev->regmap, DCU_UPDATE_MODE, + DCU_UPDATE_MODE_READREG); + while (!regmap_read(fsl_dev->regmap, DCU_UPDATE_MODE, &mode) && + mode & DCU_UPDATE_MODE_READREG); + + regmap_update_bits(fsl_dev->regmap, DCU_DCU_MODE, + DCU_MODE_DCU_MODE_MASK, + DCU_MODE_DCU_MODE(DCU_MODE_OFF)); + regmap_write(fsl_dev->regmap, DCU_UPDATE_MODE, + DCU_UPDATE_MODE_READREG); + clk_disable_unprepare(fsl_dev->pix_clk); } static void fsl_dcu_drm_crtc_enable(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; - int ret; - - ret = regmap_update_bits(fsl_dev->regmap, DCU_DCU_MODE, - DCU_MODE_DCU_MODE_MASK, - DCU_MODE_DCU_MODE(DCU_MODE_NORMAL)); - if (ret) - dev_err(fsl_dev->dev, "Enable CRTC failed\n"); - ret = regmap_write(fsl_dev->regmap, DCU_UPDATE_MODE, - DCU_UPDATE_MODE_READREG); - if (ret) - dev_err(fsl_dev->dev, "Enable CRTC failed\n"); + unsigned int mode; + + if (clk_prepare_enable(fsl_dev->pix_clk) < 0) + dev_err(fsl_dev->dev, "failed to enable pix clk\n"); + + regmap_update_bits(fsl_dev->regmap, DCU_DCU_MODE, + DCU_MODE_DCU_MODE_MASK, + DCU_MODE_DCU_MODE(DCU_MODE_NORMAL)); + regmap_write(fsl_dev->regmap, DCU_UPDATE_MODE, + DCU_UPDATE_MODE_READREG); + + /* + * Wait until transfer is complete and switch to automatic update + * mode. Automatic updates avoids flickers when changing layer + * parameters. + */ + while (!regmap_read(fsl_dev->regmap, DCU_UPDATE_MODE, &mode) && + mode & DCU_UPDATE_MODE_READREG); + regmap_write(fsl_dev->regmap, DCU_UPDATE_MODE, + DCU_UPDATE_MODE_MODE); + regmap_read(fsl_dev->regmap, DCU_UPDATE_MODE, &mode); } static bool fsl_dcu_drm_crtc_mode_fixup(struct drm_crtc *crtc, @@ -83,14 +160,12 @@ static void fsl_dcu_drm_crtc_mode_set_nofb(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + struct drm_connector *con = &fsl_dev->connector.base; struct drm_display_mode *mode = &crtc->state->mode; - unsigned int hbp, hfp, hsw, vbp, vfp, vsw, div, index; - unsigned long dcuclk; - int ret; + unsigned int hbp, hfp, hsw, vbp, vfp, vsw, index, pol = 0; index = drm_crtc_index(crtc); - dcuclk = clk_get_rate(fsl_dev->clk); - div = dcuclk / mode->clock / 1000; + clk_set_rate(fsl_dev->pix_clk, mode->clock * 1000); /* Configure timings: */ hbp = mode->htotal - mode->hsync_end; @@ -100,51 +175,37 @@ static void fsl_dcu_drm_crtc_mode_set_nofb(struct drm_crtc *crtc) vfp = mode->vsync_start - mode->vdisplay; vsw = mode->vsync_end - mode->vsync_start; - ret = regmap_write(fsl_dev->regmap, DCU_HSYN_PARA, - DCU_HSYN_PARA_BP(hbp) | - DCU_HSYN_PARA_PW(hsw) | - DCU_HSYN_PARA_FP(hfp)); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_VSYN_PARA, - DCU_VSYN_PARA_BP(vbp) | - DCU_VSYN_PARA_PW(vsw) | - DCU_VSYN_PARA_FP(vfp)); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_DISP_SIZE, - DCU_DISP_SIZE_DELTA_Y(mode->vdisplay) | - DCU_DISP_SIZE_DELTA_X(mode->hdisplay)); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_DIV_RATIO, div); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_SYN_POL, - DCU_SYN_POL_INV_VS_LOW | DCU_SYN_POL_INV_HS_LOW); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_BGND, DCU_BGND_R(0) | - DCU_BGND_G(0) | DCU_BGND_B(0)); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_DCU_MODE, - DCU_MODE_BLEND_ITER(1) | DCU_MODE_RASTER_EN); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_THRESHOLD, - DCU_THRESHOLD_LS_BF_VS(BF_VS_VAL) | - DCU_THRESHOLD_OUT_BUF_HIGH(BUF_MAX_VAL) | - DCU_THRESHOLD_OUT_BUF_LOW(BUF_MIN_VAL)); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_UPDATE_MODE, - DCU_UPDATE_MODE_READREG); - if (ret) - goto set_failed; + /* INV_PXCK as default (most display sample data on rising edge) */ + if (!(con->display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_POSEDGE)) + pol |= DCU_SYN_POL_INV_PXCK; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + pol |= DCU_SYN_POL_INV_HS_LOW; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + pol |= DCU_SYN_POL_INV_VS_LOW; + + regmap_write(fsl_dev->regmap, DCU_HSYN_PARA, + DCU_HSYN_PARA_BP(hbp) | + DCU_HSYN_PARA_PW(hsw) | + DCU_HSYN_PARA_FP(hfp)); + regmap_write(fsl_dev->regmap, DCU_VSYN_PARA, + DCU_VSYN_PARA_BP(vbp) | + DCU_VSYN_PARA_PW(vsw) | + DCU_VSYN_PARA_FP(vfp)); + regmap_write(fsl_dev->regmap, DCU_DISP_SIZE, + DCU_DISP_SIZE_DELTA_Y(mode->vdisplay) | + DCU_DISP_SIZE_DELTA_X(mode->hdisplay)); + regmap_write(fsl_dev->regmap, DCU_SYN_POL, pol); + regmap_write(fsl_dev->regmap, DCU_BGND, DCU_BGND_R(0) | + DCU_BGND_G(0) | DCU_BGND_B(0)); + regmap_write(fsl_dev->regmap, DCU_DCU_MODE, + DCU_MODE_BLEND_ITER(1) | DCU_MODE_RASTER_EN); + regmap_write(fsl_dev->regmap, DCU_THRESHOLD, + DCU_THRESHOLD_LS_BF_VS(BF_VS_VAL) | + DCU_THRESHOLD_OUT_BUF_HIGH(BUF_MAX_VAL) | + DCU_THRESHOLD_OUT_BUF_LOW(BUF_MIN_VAL)); return; -set_failed: - dev_err(dev->dev, "set DCU register failed\n"); } static const struct drm_crtc_helper_funcs fsl_dcu_drm_crtc_helper_funcs = { @@ -168,43 +229,24 @@ static const struct drm_crtc_funcs fsl_dcu_drm_crtc_funcs = { int fsl_dcu_drm_crtc_create(struct fsl_dcu_drm_device *fsl_dev) { - struct drm_plane *primary; - struct drm_crtc *crtc = &fsl_dev->crtc; - unsigned int i, j, reg_num; + struct drm_plane *primary, *cursor; + struct fsl_dcu_drm_crtc *crtc = &fsl_dev->crtc; int ret; - primary = fsl_dcu_drm_primary_create_plane(fsl_dev->drm); - ret = drm_crtc_init_with_planes(fsl_dev->drm, crtc, primary, NULL, - &fsl_dcu_drm_crtc_funcs); - if (ret < 0) + fsl_dcu_drm_init_planes(fsl_dev->drm); + + ret = fsl_dcu_drm_create_planes(fsl_dev->drm, &primary, &cursor); + if (ret) return ret; - drm_crtc_helper_add(crtc, &fsl_dcu_drm_crtc_helper_funcs); - - if (!strcmp(fsl_dev->soc->name, "ls1021a")) - reg_num = LS1021A_LAYER_REG_NUM; - else - reg_num = VF610_LAYER_REG_NUM; - for (i = 0; i <= fsl_dev->soc->total_layer; i++) { - for (j = 0; j < reg_num; j++) { - ret = regmap_write(fsl_dev->regmap, - DCU_CTRLDESCLN(i, j), 0); - if (ret) - goto init_failed; - } + ret = drm_crtc_init_with_planes(fsl_dev->drm, &crtc->base, primary, + cursor, &fsl_dcu_drm_crtc_funcs); + if (ret) { + primary->funcs->destroy(primary); + return ret; } - ret = regmap_update_bits(fsl_dev->regmap, DCU_DCU_MODE, - DCU_MODE_DCU_MODE_MASK, - DCU_MODE_DCU_MODE(DCU_MODE_OFF)); - if (ret) - goto init_failed; - ret = regmap_write(fsl_dev->regmap, DCU_UPDATE_MODE, - DCU_UPDATE_MODE_READREG); - if (ret) - goto init_failed; + + drm_crtc_helper_add(&crtc->base, &fsl_dcu_drm_crtc_helper_funcs); return 0; -init_failed: - dev_err(fsl_dev->dev, "init DCU register failed\n"); - return ret; } diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.h b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.h index 43d4da2c5fe5..23a33023d324 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.h +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.h @@ -14,6 +14,19 @@ struct fsl_dcu_drm_device; +struct fsl_dcu_drm_crtc { + struct drm_crtc base; + struct drm_pending_vblank_event *event; +}; + +static inline struct fsl_dcu_drm_crtc *to_fsl_dcu_crtc(struct drm_crtc *crtc) +{ + return crtc ? container_of(crtc, struct fsl_dcu_drm_crtc, base) + : NULL; +} + int fsl_dcu_drm_crtc_create(struct fsl_dcu_drm_device *fsl_dev); +void fsl_dcu_crtc_finish_page_flip(struct drm_device *dev); +void fsl_dcu_crtc_cancel_page_flip(struct drm_device *dev, struct drm_file *f); #endif /* __FSL_DCU_DRM_CRTC_H__ */ diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c index 1930234ba5f1..f4e318d62d22 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c @@ -22,47 +22,58 @@ #include <linux/regmap.h> #include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_flip_work.h> +#include <drm/drm_fb_cma_helper.h> #include <drm/drm_gem_cma_helper.h> #include "fsl_dcu_drm_crtc.h" #include "fsl_dcu_drm_drv.h" +#include "fsl_tcon.h" + +static int legacyfb_depth = 24; +module_param(legacyfb_depth, int, 0444); + +static bool fsl_dcu_drm_is_volatile_reg(struct device *dev, unsigned int reg) +{ + if (reg == DCU_INT_STATUS || reg == DCU_UPDATE_MODE) + return true; + + return false; +} static const struct regmap_config fsl_dcu_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, - .cache_type = REGCACHE_RBTREE, + + .volatile_reg = fsl_dcu_drm_is_volatile_reg, }; static int fsl_dcu_drm_irq_init(struct drm_device *dev) { struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; - unsigned int value; int ret; ret = drm_irq_install(dev, fsl_dev->irq); if (ret < 0) dev_err(dev->dev, "failed to install IRQ handler\n"); - ret = regmap_write(fsl_dev->regmap, DCU_INT_STATUS, 0); - if (ret) - dev_err(dev->dev, "set DCU_INT_STATUS failed\n"); - ret = regmap_read(fsl_dev->regmap, DCU_INT_MASK, &value); - if (ret) - dev_err(dev->dev, "read DCU_INT_MASK failed\n"); - value &= DCU_INT_MASK_VBLANK; - ret = regmap_write(fsl_dev->regmap, DCU_INT_MASK, value); - if (ret) - dev_err(dev->dev, "set DCU_INT_MASK failed\n"); - ret = regmap_write(fsl_dev->regmap, DCU_UPDATE_MODE, - DCU_UPDATE_MODE_READREG); - if (ret) - dev_err(dev->dev, "set DCU_UPDATE_MODE failed\n"); + regmap_write(fsl_dev->regmap, DCU_INT_STATUS, 0); + regmap_write(fsl_dev->regmap, DCU_INT_MASK, ~0); return ret; } +static void fsl_dcu_unref_worker(struct drm_flip_work *work, void *val) +{ + struct drm_atomic_state *state = val; + struct drm_device *dev = state->dev; + + fsl_dcu_cleanup_atomic_state(dev, state); +} + static int fsl_dcu_load(struct drm_device *drm, unsigned long flags) { struct device *dev = drm->dev; @@ -82,12 +93,25 @@ static int fsl_dcu_load(struct drm_device *drm, unsigned long flags) } drm->vblank_disable_allowed = true; + drm_flip_work_init(&fsl_dev->unref_work, "unref", fsl_dcu_unref_worker); + fsl_dev->unref_wq = alloc_ordered_workqueue("fsl-dcu-drm", 0); + ret = fsl_dcu_drm_irq_init(drm); if (ret < 0) goto done; drm->irq_enabled = true; - fsl_dcu_fbdev_init(drm); + if (legacyfb_depth != 16 && legacyfb_depth != 24 && + legacyfb_depth != 32) { + dev_warn(dev, "Invalid legacyfb_depth. Defaulting to 24bpp\n"); + legacyfb_depth = 24; + } + fsl_dev->fbdev = drm_fbdev_cma_init(drm, legacyfb_depth, 1, 1); + if (IS_ERR(fsl_dev->fbdev)) { + ret = PTR_ERR(fsl_dev->fbdev); + fsl_dev->fbdev = NULL; + goto done; + } return 0; done: @@ -103,9 +127,12 @@ done: static int fsl_dcu_unload(struct drm_device *dev) { + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + drm_mode_config_cleanup(dev); drm_vblank_cleanup(dev); drm_irq_uninstall(dev); + drm_flip_work_cleanup(&fsl_dev->unref_work); dev->dev_private = NULL; @@ -114,6 +141,7 @@ static int fsl_dcu_unload(struct drm_device *dev) static void fsl_dcu_drm_preclose(struct drm_device *dev, struct drm_file *file) { + fsl_dcu_crtc_cancel_page_flip(dev, file); } static irqreturn_t fsl_dcu_drm_irq(int irq, void *arg) @@ -124,18 +152,27 @@ static irqreturn_t fsl_dcu_drm_irq(int irq, void *arg) int ret; ret = regmap_read(fsl_dev->regmap, DCU_INT_STATUS, &int_status); - if (ret) - dev_err(dev->dev, "set DCU_INT_STATUS failed\n"); - if (int_status & DCU_INT_STATUS_VBLANK) + if (ret) { + dev_err(dev->dev, "read DCU_INT_STATUS failed\n"); + return IRQ_NONE; + } + + if (int_status & DCU_INT_STATUS_VBLANK) { drm_handle_vblank(dev, 0); - ret = regmap_write(fsl_dev->regmap, DCU_INT_STATUS, 0xffffffff); - if (ret) - dev_err(dev->dev, "set DCU_INT_STATUS failed\n"); - ret = regmap_write(fsl_dev->regmap, DCU_UPDATE_MODE, - DCU_UPDATE_MODE_READREG); - if (ret) - dev_err(dev->dev, "set DCU_UPDATE_MODE failed\n"); + fsl_dcu_crtc_finish_page_flip(dev); + + if (fsl_dev->cleanup_state) { + drm_flip_work_queue(&fsl_dev->unref_work, + fsl_dev->cleanup_state); + fsl_dev->cleanup_state = NULL; + + drm_flip_work_commit(&fsl_dev->unref_work, + fsl_dev->unref_wq); + } + } + + regmap_write(fsl_dev->regmap, DCU_INT_STATUS, int_status); return IRQ_HANDLED; } @@ -144,15 +181,11 @@ static int fsl_dcu_drm_enable_vblank(struct drm_device *dev, unsigned int pipe) { struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; unsigned int value; - int ret; - ret = regmap_read(fsl_dev->regmap, DCU_INT_MASK, &value); - if (ret) - dev_err(dev->dev, "read DCU_INT_MASK failed\n"); + regmap_read(fsl_dev->regmap, DCU_INT_MASK, &value); value &= ~DCU_INT_MASK_VBLANK; - ret = regmap_write(fsl_dev->regmap, DCU_INT_MASK, value); - if (ret) - dev_err(dev->dev, "set DCU_INT_MASK failed\n"); + regmap_write(fsl_dev->regmap, DCU_INT_MASK, value); + return 0; } @@ -161,15 +194,17 @@ static void fsl_dcu_drm_disable_vblank(struct drm_device *dev, { struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; unsigned int value; - int ret; - ret = regmap_read(fsl_dev->regmap, DCU_INT_MASK, &value); - if (ret) - dev_err(dev->dev, "read DCU_INT_MASK failed\n"); + regmap_read(fsl_dev->regmap, DCU_INT_MASK, &value); value |= DCU_INT_MASK_VBLANK; - ret = regmap_write(fsl_dev->regmap, DCU_INT_MASK, value); - if (ret) - dev_err(dev->dev, "set DCU_INT_MASK failed\n"); + regmap_write(fsl_dev->regmap, DCU_INT_MASK, value); +} + +static void fsl_dcu_drm_lastclose(struct drm_device *dev) +{ + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + + drm_fbdev_cma_restore_mode(fsl_dev->fbdev); } static const struct file_operations fsl_dcu_drm_fops = { @@ -189,6 +224,7 @@ static const struct file_operations fsl_dcu_drm_fops = { static struct drm_driver fsl_dcu_drm_driver = { .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC, + .lastclose = fsl_dcu_drm_lastclose, .load = fsl_dcu_load, .unload = fsl_dcu_unload, .preclose = fsl_dcu_drm_preclose, @@ -226,11 +262,18 @@ static int fsl_dcu_drm_pm_suspend(struct device *dev) if (!fsl_dev) return 0; + disable_irq(fsl_dev->irq); drm_kms_helper_poll_disable(fsl_dev->drm); - regcache_cache_only(fsl_dev->regmap, true); - regcache_mark_dirty(fsl_dev->regmap); - clk_disable(fsl_dev->clk); - clk_unprepare(fsl_dev->clk); + fsl_dcu_fbdev_suspend(fsl_dev->drm); + + fsl_dev->state = drm_atomic_helper_suspend(fsl_dev->drm); + if (IS_ERR(fsl_dev->state)) { + fsl_dcu_fbdev_resume(fsl_dev->drm); + enable_irq(fsl_dev->irq); + return PTR_ERR(fsl_dev->state); + } + + clk_disable_unprepare(fsl_dev->clk); return 0; } @@ -243,21 +286,22 @@ static int fsl_dcu_drm_pm_resume(struct device *dev) if (!fsl_dev) return 0; - ret = clk_enable(fsl_dev->clk); + ret = clk_prepare_enable(fsl_dev->clk); if (ret < 0) { dev_err(dev, "failed to enable dcu clk\n"); - clk_unprepare(fsl_dev->clk); - return ret; - } - ret = clk_prepare(fsl_dev->clk); - if (ret < 0) { - dev_err(dev, "failed to prepare dcu clk\n"); return ret; } + fsl_tcon_bypass_enable(fsl_dev->tcon); + fsl_dcu_drm_init_planes(fsl_dev->drm); + + drm_atomic_helper_resume(fsl_dev->drm, fsl_dev->state); + + regmap_write(fsl_dev->regmap, DCU_INT_MASK, fsl_dev->irq_state); + + fsl_dcu_fbdev_resume(fsl_dev->drm); drm_kms_helper_poll_enable(fsl_dev->drm); - regcache_cache_only(fsl_dev->regmap, false); - regcache_sync(fsl_dev->regmap); + enable_irq(fsl_dev->irq); return 0; } @@ -271,12 +315,14 @@ static const struct fsl_dcu_soc_data fsl_dcu_ls1021a_data = { .name = "ls1021a", .total_layer = 16, .max_layer = 4, + .layer_regs = LS1021A_LAYER_REG_NUM, }; static const struct fsl_dcu_soc_data fsl_dcu_vf610_data = { .name = "vf610", .total_layer = 64, .max_layer = 6, + .layer_regs = VF610_LAYER_REG_NUM, }; static const struct of_device_id fsl_dcu_of_match[] = { @@ -299,6 +345,9 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev) struct resource *res; void __iomem *base; struct drm_driver *driver = &fsl_dcu_drm_driver; + struct clk *pix_clk_in; + char pix_clk_name[32]; + const char *pix_clk_in_name; const struct of_device_id *id; int ret; @@ -306,6 +355,11 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev) if (!fsl_dev) return -ENOMEM; + id = of_match_node(fsl_dcu_of_match, pdev->dev.of_node); + if (!id) + return -ENODEV; + fsl_dev->soc = id->data; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(dev, "could not get memory IO resource\n"); @@ -324,40 +378,48 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev) return -ENXIO; } + fsl_dev->regmap = devm_regmap_init_mmio(dev, base, + &fsl_dcu_regmap_config); + if (IS_ERR(fsl_dev->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(fsl_dev->regmap); + } + fsl_dev->clk = devm_clk_get(dev, "dcu"); if (IS_ERR(fsl_dev->clk)) { - ret = PTR_ERR(fsl_dev->clk); dev_err(dev, "failed to get dcu clock\n"); - return ret; - } - ret = clk_prepare(fsl_dev->clk); - if (ret < 0) { - dev_err(dev, "failed to prepare dcu clk\n"); - return ret; + return PTR_ERR(fsl_dev->clk); } - ret = clk_enable(fsl_dev->clk); + ret = clk_prepare_enable(fsl_dev->clk); if (ret < 0) { dev_err(dev, "failed to enable dcu clk\n"); - clk_unprepare(fsl_dev->clk); return ret; } - fsl_dev->regmap = devm_regmap_init_mmio(dev, base, - &fsl_dcu_regmap_config); - if (IS_ERR(fsl_dev->regmap)) { - dev_err(dev, "regmap init failed\n"); - return PTR_ERR(fsl_dev->regmap); + pix_clk_in = devm_clk_get(dev, "pix"); + if (IS_ERR(pix_clk_in)) { + /* legancy binding, use dcu clock as pixel clock input */ + pix_clk_in = fsl_dev->clk; } - id = of_match_node(fsl_dcu_of_match, pdev->dev.of_node); - if (!id) - return -ENODEV; - fsl_dev->soc = id->data; + pix_clk_in_name = __clk_get_name(pix_clk_in); + snprintf(pix_clk_name, sizeof(pix_clk_name), "%s_pix", pix_clk_in_name); + fsl_dev->pix_clk = clk_register_divider(dev, pix_clk_name, + pix_clk_in_name, 0, base + DCU_DIV_RATIO, + 0, 8, CLK_DIVIDER_ROUND_CLOSEST, NULL); + if (IS_ERR(fsl_dev->pix_clk)) { + dev_err(dev, "failed to register pix clk\n"); + ret = PTR_ERR(fsl_dev->pix_clk); + goto disable_clk; + } drm = drm_dev_alloc(driver, dev); - if (!drm) - return -ENOMEM; + if (!drm) { + ret = -ENOMEM; + goto unregister_pix_clk; + } + fsl_dev->tcon = fsl_tcon_init(dev); fsl_dev->dev = dev; fsl_dev->drm = drm; fsl_dev->np = dev->of_node; @@ -377,6 +439,10 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev) unref: drm_dev_unref(drm); +unregister_pix_clk: + clk_unregister(fsl_dev->pix_clk); +disable_clk: + clk_disable_unprepare(fsl_dev->clk); return ret; } @@ -384,6 +450,9 @@ static int fsl_dcu_drm_remove(struct platform_device *pdev) { struct fsl_dcu_drm_device *fsl_dev = platform_get_drvdata(pdev); + clk_disable_unprepare(fsl_dev->clk); + clk_disable_unprepare(fsl_dev->pix_clk); + clk_unregister(fsl_dev->pix_clk); drm_put_dev(fsl_dev->drm); return 0; diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h index 579b9e44e764..22210766e77d 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h @@ -47,8 +47,8 @@ #define DCU_VSYN_PARA_FP(x) (x) #define DCU_SYN_POL 0x0024 -#define DCU_SYN_POL_INV_PXCK_FALL (0 << 6) -#define DCU_SYN_POL_NEG_REMAIN (0 << 5) +#define DCU_SYN_POL_INV_PXCK BIT(6) +#define DCU_SYN_POL_NEG BIT(5) #define DCU_SYN_POL_INV_VS_LOW BIT(1) #define DCU_SYN_POL_INV_HS_LOW BIT(0) @@ -118,11 +118,11 @@ #define DCU_CTRLDESCLN(layer, reg) (0x200 + (reg - 1) * 4 + (layer) * 0x40) -#define DCU_LAYER_HEIGHT(x) ((x) << 16) -#define DCU_LAYER_WIDTH(x) (x) +#define DCU_LAYER_HEIGHT(x) (((x) & 0x7ff) << 16) +#define DCU_LAYER_WIDTH(x) ((x) & 0x7ff) -#define DCU_LAYER_POSY(x) ((x) << 16) -#define DCU_LAYER_POSX(x) (x) +#define DCU_LAYER_POSY(x) (((x) & 0xfff) << 16) +#define DCU_LAYER_POSX(x) ((x) & 0xfff) #define DCU_LAYER_EN BIT(31) #define DCU_LAYER_TILE_EN BIT(30) @@ -133,7 +133,9 @@ #define DCU_LAYER_RLE_EN BIT(15) #define DCU_LAYER_LUOFFS(x) ((x) << 4) #define DCU_LAYER_BB_ON BIT(2) -#define DCU_LAYER_AB(x) (x) +#define DCU_LAYER_AB_NONE 0 +#define DCU_LAYER_AB_CHROMA_KEYING 1 +#define DCU_LAYER_AB_WHOLE_FRAME 2 #define DCU_LAYER_CKMAX_R(x) ((x) << 16) #define DCU_LAYER_CKMAX_G(x) ((x) << 8) @@ -166,6 +168,7 @@ struct clk; struct device; struct drm_device; +struct drm_flip_work; struct fsl_dcu_soc_data { const char *name; @@ -173,6 +176,7 @@ struct fsl_dcu_soc_data { unsigned int total_layer; /*max layer number DCU supported*/ unsigned int max_layer; + unsigned int layer_regs; }; struct fsl_dcu_drm_device { @@ -181,17 +185,27 @@ struct fsl_dcu_drm_device { struct regmap *regmap; int irq; struct clk *clk; + struct clk *pix_clk; + struct fsl_tcon *tcon; /*protects hardware register*/ spinlock_t irq_lock; struct drm_device *drm; struct drm_fbdev_cma *fbdev; - struct drm_crtc crtc; + struct fsl_dcu_drm_crtc crtc; struct drm_encoder encoder; struct fsl_dcu_drm_connector connector; const struct fsl_dcu_soc_data *soc; + struct drm_atomic_state *cleanup_state; + struct workqueue_struct *unref_wq; + struct drm_flip_work unref_work; + struct drm_atomic_state *state; + unsigned int irq_state; }; -void fsl_dcu_fbdev_init(struct drm_device *dev); +void fsl_dcu_fbdev_suspend(struct drm_device *dev); +void fsl_dcu_fbdev_resume(struct drm_device *dev); int fsl_dcu_drm_modeset_init(struct fsl_dcu_drm_device *fsl_dev); +void fsl_dcu_cleanup_atomic_state(struct drm_device *dev, + struct drm_atomic_state *state); #endif /* __FSL_DCU_DRM_DRV_H__ */ diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_fbdev.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_fbdev.c index 8b8b819ea704..eaa447e26423 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_fbdev.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_fbdev.c @@ -9,15 +9,29 @@ * (at your option) any later version. */ +#include <linux/console.h> + #include <drm/drmP.h> #include <drm/drm_fb_cma_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_flip_work.h> #include "fsl_dcu_drm_drv.h" -/* initialize fbdev helper */ -void fsl_dcu_fbdev_init(struct drm_device *dev) +void fsl_dcu_fbdev_suspend(struct drm_device *dev) +{ + struct fsl_dcu_drm_device *fsl_dev = dev_get_drvdata(dev->dev); + + console_lock(); + drm_fb_helper_set_suspend(drm_fbdev_cma_get_helper(fsl_dev->fbdev), 1); + console_unlock(); +} + +void fsl_dcu_fbdev_resume(struct drm_device *dev) { struct fsl_dcu_drm_device *fsl_dev = dev_get_drvdata(dev->dev); - fsl_dev->fbdev = drm_fbdev_cma_init(dev, 24, 1, 1); + console_lock(); + drm_fb_helper_set_suspend(drm_fbdev_cma_get_helper(fsl_dev->fbdev), 0); + console_unlock(); } diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_kms.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_kms.c index 0ef5959710e7..710d1ca7a9b4 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_kms.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_kms.c @@ -10,21 +10,64 @@ */ #include <drm/drmP.h> +#include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_flip_work.h> #include <drm/drm_fb_cma_helper.h> #include "fsl_dcu_drm_crtc.h" #include "fsl_dcu_drm_drv.h" +void fsl_dcu_cleanup_atomic_state(struct drm_device *dev, + struct drm_atomic_state *state) +{ + drm_atomic_helper_cleanup_planes(dev, state); + drm_atomic_state_free(state); +} + +static int fsl_dcu_drm_atomic_commit(struct drm_device *dev, + struct drm_atomic_state *state, + bool async) +{ + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + int ret; + + ret = drm_atomic_helper_prepare_planes(dev, state); + if (ret < 0) + return ret; + + /* + * This is the point of no return - everything below never fails except + * when the hw goes bonghits. Which means we can commit the new state on + * the software side now. + */ + drm_atomic_helper_swap_state(dev, state); + + drm_atomic_helper_commit_modeset_disables(dev, state); + drm_atomic_helper_commit_planes(dev, state, false); + drm_atomic_helper_commit_modeset_enables(dev, state); + + if (async) { + fsl_dev->cleanup_state = state; + } else { + drm_atomic_helper_wait_for_vblanks(dev, state); + fsl_dcu_cleanup_atomic_state(dev, state); + } + + return 0; +} + static const struct drm_mode_config_funcs fsl_dcu_drm_mode_config_funcs = { .atomic_check = drm_atomic_helper_check, - .atomic_commit = drm_atomic_helper_commit, + .atomic_commit = fsl_dcu_drm_atomic_commit, .fb_create = drm_fb_cma_create, }; int fsl_dcu_drm_modeset_init(struct fsl_dcu_drm_device *fsl_dev) { + int ret; + drm_mode_config_init(fsl_dev->drm); fsl_dev->drm->mode_config.min_width = 0; @@ -33,11 +76,25 @@ int fsl_dcu_drm_modeset_init(struct fsl_dcu_drm_device *fsl_dev) fsl_dev->drm->mode_config.max_height = 2047; fsl_dev->drm->mode_config.funcs = &fsl_dcu_drm_mode_config_funcs; - drm_kms_helper_poll_init(fsl_dev->drm); - fsl_dcu_drm_crtc_create(fsl_dev); - fsl_dcu_drm_encoder_create(fsl_dev, &fsl_dev->crtc); - fsl_dcu_drm_connector_create(fsl_dev, &fsl_dev->encoder); + ret = fsl_dcu_drm_crtc_create(fsl_dev); + if (ret) + return ret; + + ret = fsl_dcu_drm_encoder_create(fsl_dev, &fsl_dev->crtc.base); + if (ret) + goto fail_encoder; + + ret = fsl_dcu_drm_connector_create(fsl_dev, &fsl_dev->encoder); + if (ret) + goto fail_connector; + drm_mode_config_reset(fsl_dev->drm); + drm_kms_helper_poll_init(fsl_dev->drm); return 0; +fail_encoder: + fsl_dev->crtc.base.funcs->destroy(&fsl_dev->crtc.base); +fail_connector: + fsl_dev->encoder.funcs->destroy(&fsl_dev->encoder); + return ret; } diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.c index 51daaea40b4d..3ad0debacadf 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.c @@ -15,6 +15,7 @@ #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_flip_work.h> #include <drm/drm_fb_cma_helper.h> #include <drm/drm_gem_cma_helper.h> #include <drm/drm_plane_helper.h> @@ -41,11 +42,17 @@ static int fsl_dcu_drm_plane_atomic_check(struct drm_plane *plane, { struct drm_framebuffer *fb = state->fb; + if (!state->fb || !state->crtc) + return 0; + switch (fb->pixel_format) { case DRM_FORMAT_RGB565: case DRM_FORMAT_RGB888: + case DRM_FORMAT_XRGB8888: case DRM_FORMAT_ARGB8888: - case DRM_FORMAT_BGRA4444: + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_XRGB1555: case DRM_FORMAT_ARGB1555: case DRM_FORMAT_YUV422: return 0; @@ -59,19 +66,15 @@ static void fsl_dcu_drm_plane_atomic_disable(struct drm_plane *plane, { struct fsl_dcu_drm_device *fsl_dev = plane->dev->dev_private; unsigned int value; - int index, ret; + int index; index = fsl_dcu_drm_plane_index(plane); if (index < 0) return; - ret = regmap_read(fsl_dev->regmap, DCU_CTRLDESCLN(index, 4), &value); - if (ret) - dev_err(fsl_dev->dev, "read DCU_INT_MASK failed\n"); + regmap_read(fsl_dev->regmap, DCU_CTRLDESCLN(index, 4), &value); value &= ~DCU_LAYER_EN; - ret = regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 4), value); - if (ret) - dev_err(fsl_dev->dev, "set DCU register failed\n"); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 4), value); } static void fsl_dcu_drm_plane_atomic_update(struct drm_plane *plane, @@ -82,8 +85,8 @@ static void fsl_dcu_drm_plane_atomic_update(struct drm_plane *plane, struct drm_plane_state *state = plane->state; struct drm_framebuffer *fb = plane->state->fb; struct drm_gem_cma_object *gem; - unsigned int alpha, bpp; - int index, ret; + unsigned int alpha = DCU_LAYER_AB_NONE, bpp; + int index; if (!fb) return; @@ -97,96 +100,69 @@ static void fsl_dcu_drm_plane_atomic_update(struct drm_plane *plane, switch (fb->pixel_format) { case DRM_FORMAT_RGB565: bpp = FSL_DCU_RGB565; - alpha = 0xff; break; case DRM_FORMAT_RGB888: bpp = FSL_DCU_RGB888; - alpha = 0xff; break; case DRM_FORMAT_ARGB8888: + alpha = DCU_LAYER_AB_WHOLE_FRAME; + /* fall-through */ + case DRM_FORMAT_XRGB8888: bpp = FSL_DCU_ARGB8888; - alpha = 0xff; break; - case DRM_FORMAT_BGRA4444: + case DRM_FORMAT_ARGB4444: + alpha = DCU_LAYER_AB_WHOLE_FRAME; + /* fall-through */ + case DRM_FORMAT_XRGB4444: bpp = FSL_DCU_ARGB4444; - alpha = 0xff; break; case DRM_FORMAT_ARGB1555: + alpha = DCU_LAYER_AB_WHOLE_FRAME; + /* fall-through */ + case DRM_FORMAT_XRGB1555: bpp = FSL_DCU_ARGB1555; - alpha = 0xff; break; case DRM_FORMAT_YUV422: bpp = FSL_DCU_YUV422; - alpha = 0xff; break; default: return; } - ret = regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 1), - DCU_LAYER_HEIGHT(state->crtc_h) | - DCU_LAYER_WIDTH(state->crtc_w)); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 2), - DCU_LAYER_POSY(state->crtc_y) | - DCU_LAYER_POSX(state->crtc_x)); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, - DCU_CTRLDESCLN(index, 3), gem->paddr); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 4), - DCU_LAYER_EN | - DCU_LAYER_TRANS(alpha) | - DCU_LAYER_BPP(bpp) | - DCU_LAYER_AB(0)); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 5), - DCU_LAYER_CKMAX_R(0xFF) | - DCU_LAYER_CKMAX_G(0xFF) | - DCU_LAYER_CKMAX_B(0xFF)); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 6), - DCU_LAYER_CKMIN_R(0) | - DCU_LAYER_CKMIN_G(0) | - DCU_LAYER_CKMIN_B(0)); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 7), 0); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 8), - DCU_LAYER_FG_FCOLOR(0)); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 9), - DCU_LAYER_BG_BCOLOR(0)); - if (ret) - goto set_failed; + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 1), + DCU_LAYER_HEIGHT(state->crtc_h) | + DCU_LAYER_WIDTH(state->crtc_w)); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 2), + DCU_LAYER_POSY(state->crtc_y) | + DCU_LAYER_POSX(state->crtc_x)); + regmap_write(fsl_dev->regmap, + DCU_CTRLDESCLN(index, 3), gem->paddr); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 4), + DCU_LAYER_EN | + DCU_LAYER_TRANS(0xff) | + DCU_LAYER_BPP(bpp) | + alpha); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 5), + DCU_LAYER_CKMAX_R(0xFF) | + DCU_LAYER_CKMAX_G(0xFF) | + DCU_LAYER_CKMAX_B(0xFF)); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 6), + DCU_LAYER_CKMIN_R(0) | + DCU_LAYER_CKMIN_G(0) | + DCU_LAYER_CKMIN_B(0)); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 7), 0); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 8), + DCU_LAYER_FG_FCOLOR(0)); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 9), + DCU_LAYER_BG_BCOLOR(0)); + if (!strcmp(fsl_dev->soc->name, "ls1021a")) { - ret = regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 10), - DCU_LAYER_POST_SKIP(0) | - DCU_LAYER_PRE_SKIP(0)); - if (ret) - goto set_failed; + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 10), + DCU_LAYER_POST_SKIP(0) | + DCU_LAYER_PRE_SKIP(0)); } - ret = regmap_update_bits(fsl_dev->regmap, DCU_DCU_MODE, - DCU_MODE_DCU_MODE_MASK, - DCU_MODE_DCU_MODE(DCU_MODE_NORMAL)); - if (ret) - goto set_failed; - ret = regmap_write(fsl_dev->regmap, - DCU_UPDATE_MODE, DCU_UPDATE_MODE_READREG); - if (ret) - goto set_failed; - return; -set_failed: - dev_err(fsl_dev->dev, "set DCU register failed\n"); + return; } static void @@ -213,6 +189,7 @@ static const struct drm_plane_helper_funcs fsl_dcu_drm_plane_helper_funcs = { static void fsl_dcu_drm_plane_destroy(struct drm_plane *plane) { drm_plane_cleanup(plane); + kfree(plane); } static const struct drm_plane_funcs fsl_dcu_drm_plane_funcs = { @@ -227,34 +204,69 @@ static const struct drm_plane_funcs fsl_dcu_drm_plane_funcs = { static const u32 fsl_dcu_drm_plane_formats[] = { DRM_FORMAT_RGB565, DRM_FORMAT_RGB888, + DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB4444, DRM_FORMAT_ARGB4444, + DRM_FORMAT_XRGB1555, DRM_FORMAT_ARGB1555, DRM_FORMAT_YUV422, }; -struct drm_plane *fsl_dcu_drm_primary_create_plane(struct drm_device *dev) +void fsl_dcu_drm_init_planes(struct drm_device *dev) { - struct drm_plane *primary; - int ret; + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + int i, j; - primary = kzalloc(sizeof(*primary), GFP_KERNEL); - if (!primary) { - DRM_DEBUG_KMS("Failed to allocate primary plane\n"); - return NULL; + for (i = 0; i < fsl_dev->soc->total_layer; i++) { + for (j = 1; j <= fsl_dev->soc->layer_regs; j++) + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(i, j), 0); } +} + +int fsl_dcu_drm_create_planes(struct drm_device *dev, struct drm_plane **primary, + struct drm_plane **cursor) +{ + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + struct drm_plane *planes, *plane; + int total_layer = fsl_dev->soc->total_layer; + int ret, i; - /* possible_crtc's will be filled in later by crtc_init */ - ret = drm_universal_plane_init(dev, primary, 0, + planes = devm_kzalloc(dev->dev, sizeof(struct drm_plane) * total_layer, + GFP_KERNEL); + if (!planes) { + DRM_DEBUG_KMS("Failed to allocate planes\n"); + return -ENOMEM; + } + + plane = planes; + + for (i = 0; i < total_layer; i++) { + enum drm_plane_type type = DRM_PLANE_TYPE_OVERLAY; + if (i == 0) { + type = DRM_PLANE_TYPE_PRIMARY; + *primary = plane; + } else if (i == total_layer - 1) { + type = DRM_PLANE_TYPE_CURSOR; + *cursor = plane; + } + + ret = drm_universal_plane_init(dev, plane, 1, &fsl_dcu_drm_plane_funcs, fsl_dcu_drm_plane_formats, ARRAY_SIZE(fsl_dcu_drm_plane_formats), - DRM_PLANE_TYPE_PRIMARY); - if (ret) { - kfree(primary); - primary = NULL; + type); + if (ret) + goto err_cleanup_planes; + + drm_plane_helper_add(plane, &fsl_dcu_drm_plane_helper_funcs); + plane++; } - drm_plane_helper_add(primary, &fsl_dcu_drm_plane_helper_funcs); - return primary; + return 0; + +err_cleanup_planes: + list_for_each_entry(plane, &dev->mode_config.plane_list, head) + drm_plane_cleanup(plane); + return ret; } diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.h b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.h index d657f088d859..be3604fb43ce 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.h +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.h @@ -12,6 +12,8 @@ #ifndef __FSL_DCU_DRM_PLANE_H__ #define __FSL_DCU_DRM_PLANE_H__ -struct drm_plane *fsl_dcu_drm_primary_create_plane(struct drm_device *dev); +void fsl_dcu_drm_init_planes(struct drm_device *dev); +int fsl_dcu_drm_create_planes(struct drm_device *dev, struct drm_plane **primary, + struct drm_plane **cursor); #endif /* __FSL_DCU_DRM_PLANE_H__ */ diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c index fe8ab5da04fb..4246129972b6 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c @@ -14,9 +14,11 @@ #include <drm/drmP.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_flip_work.h> #include <drm/drm_panel.h> #include "fsl_dcu_drm_drv.h" +#include "fsl_tcon.h" static int fsl_dcu_drm_encoder_atomic_check(struct drm_encoder *encoder, @@ -56,6 +58,10 @@ int fsl_dcu_drm_encoder_create(struct fsl_dcu_drm_device *fsl_dev, int ret; encoder->possible_crtcs = 1; + + /* Set TCON to bypass for parallel RGB/LVDS */ + fsl_tcon_bypass_enable(fsl_dev->tcon); + ret = drm_encoder_init(fsl_dev->drm, encoder, &encoder_funcs, DRM_MODE_ENCODER_LVDS); if (ret < 0) diff --git a/drivers/gpu/drm/fsl-dcu/fsl_tcon.c b/drivers/gpu/drm/fsl-dcu/fsl_tcon.c new file mode 100644 index 000000000000..e5001a186850 --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_tcon.c @@ -0,0 +1,108 @@ +/* + * Copyright 2015 Toradex AG + * + * Stefan Agner <stefan@agner.ch> + * + * Freescale TCON device driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/mm.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include "fsl_tcon.h" + +void fsl_tcon_bypass_disable(struct fsl_tcon *tcon) +{ + regmap_update_bits(tcon->regs, FSL_TCON_CTRL1, + FSL_TCON_CTRL1_TCON_BYPASS, 0); +} + +void fsl_tcon_bypass_enable(struct fsl_tcon *tcon) +{ + regmap_update_bits(tcon->regs, FSL_TCON_CTRL1, + FSL_TCON_CTRL1_TCON_BYPASS, + FSL_TCON_CTRL1_TCON_BYPASS); +} + +static struct regmap_config fsl_tcon_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .name = "tcon", +}; + +static int fsl_tcon_init_regmap(struct device *dev, + struct fsl_tcon *tcon, + struct device_node *np) +{ + struct resource res; + void __iomem *regs; + + if (of_address_to_resource(np, 0, &res)) + return -EINVAL; + + regs = devm_ioremap_resource(dev, &res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + tcon->regs = devm_regmap_init_mmio(dev, regs, + &fsl_tcon_regmap_config); + if (IS_ERR(tcon->regs)) + return PTR_ERR(tcon->regs); + + return 0; +} + +struct fsl_tcon *fsl_tcon_init(struct device *dev) +{ + struct fsl_tcon *tcon; + struct device_node *np; + int ret; + + tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL); + if (!tcon) + return NULL; + + np = of_parse_phandle(dev->of_node, "fsl,tcon", 0); + if (!np) { + dev_warn(dev, "Couldn't find the tcon node\n"); + return NULL; + } + + ret = fsl_tcon_init_regmap(dev, tcon, np); + if (ret) { + dev_err(dev, "Couldn't create the TCON regmap\n"); + goto err_node_put; + } + + tcon->ipg_clk = of_clk_get_by_name(np, "ipg"); + if (IS_ERR(tcon->ipg_clk)) { + dev_err(dev, "Couldn't get the TCON bus clock\n"); + goto err_node_put; + } + + clk_prepare_enable(tcon->ipg_clk); + + return tcon; + +err_node_put: + of_node_put(np); + return NULL; +} + +void fsl_tcon_free(struct fsl_tcon *tcon) +{ + clk_disable_unprepare(tcon->ipg_clk); + clk_put(tcon->ipg_clk); +} + diff --git a/drivers/gpu/drm/fsl-dcu/fsl_tcon.h b/drivers/gpu/drm/fsl-dcu/fsl_tcon.h new file mode 100644 index 000000000000..80a7617de58f --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_tcon.h @@ -0,0 +1,33 @@ +/* + * Copyright 2015 Toradex AG + * + * Stefan Agner <stefan@agner.ch> + * + * Freescale TCON device driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __FSL_TCON_H__ +#define __FSL_TCON_H__ + +#include <linux/bitops.h> + +#define FSL_TCON_CTRL1 0x0 +#define FSL_TCON_CTRL1_TCON_BYPASS BIT(29) + +struct fsl_tcon { + struct regmap *regs; + struct clk *ipg_clk; +}; + +struct fsl_tcon *fsl_tcon_init(struct device *dev); +void fsl_tcon_free(struct fsl_tcon *tcon); + +void fsl_tcon_bypass_disable(struct fsl_tcon *tcon); +void fsl_tcon_bypass_enable(struct fsl_tcon *tcon); + +#endif /* __FSL_TCON_H__ */ diff --git a/drivers/gpu/drm/imx/imx-drm-core.c b/drivers/gpu/drm/imx/imx-drm-core.c index 5378bdc3bbf9..5f936223e67b 100644 --- a/drivers/gpu/drm/imx/imx-drm-core.c +++ b/drivers/gpu/drm/imx/imx-drm-core.c @@ -313,6 +313,7 @@ static int imx_drm_driver_load(struct drm_device *drm, unsigned long flags) dev_warn(drm->dev, "Invalid legacyfb_depth. Defaulting to 16bpp\n"); legacyfb_depth = 16; } + drm_helper_disable_unused_functions(drm); imxdrm->fbhelper = drm_fbdev_cma_init(drm, legacyfb_depth, drm->mode_config.num_crtc, MAX_CRTC); if (IS_ERR(imxdrm->fbhelper)) { diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index f418c002d323..5101e2544f96 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -68,6 +68,7 @@ struct panel_desc { } delay; u32 bus_format; + u32 bus_flags; }; struct panel_simple { @@ -140,6 +141,7 @@ static int panel_simple_get_fixed_modes(struct panel_simple *panel) if (panel->desc->bus_format) drm_display_info_set_bus_formats(&connector->display_info, &panel->desc->bus_format, 1); + connector->display_info.bus_flags = panel->desc->bus_flags; return num; } @@ -951,6 +953,29 @@ static const struct panel_desc lg_lp129qe = { }, }; +static const struct drm_display_mode logic_lt161010_2nhc_mode = { + .clock = 33300, + .hdisplay = 800, + .hsync_start = 800 + 40, + .hsync_end = 800 + 40 + 128, + .htotal = 800 + 40 + 128 + 88, + .vdisplay = 480, + .vsync_start = 480 + 10, + .vsync_end = 480 + 2 + 10, + .vtotal = 480 + 2 + 10 + 33, + .vrefresh = 60, +}; + +static const struct panel_desc logic_lt161010_2nhc = { + .modes = &logic_lt161010_2nhc_mode, + .num_modes = 1, + .size = { + .width = 165, + .height = 100, + }, + .bus_flags = DRM_BUS_FLAG_PIXDATA_POSEDGE, +}; + static const struct drm_display_mode nec_nl4827hc19_05b_mode = { .clock = 10870, .hdisplay = 480, @@ -962,6 +987,7 @@ static const struct drm_display_mode nec_nl4827hc19_05b_mode = { .vsync_end = 272 + 2 + 4, .vtotal = 272 + 2 + 4 + 2, .vrefresh = 74, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, }; static const struct panel_desc nec_nl4827hc19_05b = { @@ -972,7 +998,8 @@ static const struct panel_desc nec_nl4827hc19_05b = { .width = 95, .height = 54, }, - .bus_format = MEDIA_BUS_FMT_RGB888_1X24 + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_PIXDATA_POSEDGE, }; static const struct display_timing okaya_rs800480t_7x0gp_timing = { @@ -1098,6 +1125,51 @@ static const struct panel_desc shelly_sca07010_bfn_lnn = { .bus_format = MEDIA_BUS_FMT_RGB666_1X18, }; +static const struct drm_display_mode tpk_f07a_0102_mode = { + .clock = 33260, + .hdisplay = 800, + .hsync_start = 800 + 40, + .hsync_end = 800 + 40 + 128, + .htotal = 800 + 40 + 128 + 88, + .vdisplay = 480, + .vsync_start = 480 + 10, + .vsync_end = 480 + 10 + 2, + .vtotal = 480 + 10 + 2 + 33, + .vrefresh = 60, +}; + +static const struct panel_desc tpk_f07a_0102 = { + .modes = &tpk_f07a_0102_mode, + .num_modes = 1, + .size = { + .width = 152, + .height = 91, + }, + .bus_flags = DRM_BUS_FLAG_PIXDATA_POSEDGE, +}; + +static const struct drm_display_mode tpk_f10a_0102_mode = { + .clock = 45000, + .hdisplay = 1024, + .hsync_start = 1024 + 176, + .hsync_end = 1024 + 176 + 5, + .htotal = 1024 + 176 + 5 + 88, + .vdisplay = 600, + .vsync_start = 600 + 20, + .vsync_end = 600 + 20 + 5, + .vtotal = 600 + 20 + 5 + 25, + .vrefresh = 60, +}; + +static const struct panel_desc tpk_f10a_0102 = { + .modes = &tpk_f10a_0102_mode, + .num_modes = 1, + .size = { + .width = 223, + .height = 125, + }, +}; + static const struct of_device_id platform_of_match[] = { { .compatible = "ampire,am800480r3tmqwa1h", @@ -1175,6 +1247,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "lg,lp129qe", .data = &lg_lp129qe, }, { + .compatible = "logic,lt161010-2nhc", + .data = &logic_lt161010_2nhc, + }, { .compatible = "nec,nl4827hc19-05b", .data = &nec_nl4827hc19_05b, }, { @@ -1193,6 +1268,12 @@ static const struct of_device_id platform_of_match[] = { .compatible = "shelly,sca07010-bfn-lnn", .data = &shelly_sca07010_bfn_lnn, }, { + .compatible = "tpk,f07a-0102", + .data = &tpk_f07a_0102, + }, { + .compatible = "tpk,f10a-0102", + .data = &tpk_f10a_0102, + }, { /* sentinel */ } }; diff --git a/drivers/gpu/drm/sti/sti_drv.c b/drivers/gpu/drm/sti/sti_drv.c index 1469987949d8..506b5626f3ed 100644 --- a/drivers/gpu/drm/sti/sti_drv.c +++ b/drivers/gpu/drm/sti/sti_drv.c @@ -160,6 +160,7 @@ static int sti_load(struct drm_device *dev, unsigned long flags) drm_mode_config_reset(dev); + drm_helper_disable_unused_functions(dev); drm_fbdev_cma_init(dev, 32, dev->mode_config.num_crtc, dev->mode_config.num_connector); diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c index 876cad58b1f9..24be31d69701 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c @@ -294,6 +294,7 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags) break; } + drm_helper_disable_unused_functions(dev); priv->fbdev = drm_fbdev_cma_init(dev, bpp, dev->mode_config.num_crtc, dev->mode_config.num_connector); diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index cf1b57a054d0..61deb6e65dfd 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -987,11 +987,24 @@ static void i2c_imx_unprepare_recovery(struct i2c_adapter *adap) pinctrl_select_state(i2c_imx->pinctrl, i2c_imx->pinctrl_pins_default); } -static void i2c_imx_init_recovery_info(struct imx_i2c_struct *i2c_imx, +/* + * We switch SCL and SDA to their GPIO function and do some bitbanging + * for bus recovery. These alternative pinmux settings can be + * described in the device tree by a separate pinctrl state "gpio". If + * this is missing this is not a big problem, the only implication is + * that we can't do bus recovery. + */ +static int i2c_imx_init_recovery_info(struct imx_i2c_struct *i2c_imx, struct platform_device *pdev) { struct i2c_bus_recovery_info *rinfo = &i2c_imx->rinfo; + i2c_imx->pinctrl = devm_pinctrl_get(&pdev->dev); + if (!i2c_imx->pinctrl || IS_ERR(i2c_imx->pinctrl)) { + dev_info(&pdev->dev, "can't get pinctrl, bus recovery not supported\n"); + return PTR_ERR(i2c_imx->pinctrl); + } + i2c_imx->pinctrl_pins_default = pinctrl_lookup_state(i2c_imx->pinctrl, PINCTRL_STATE_DEFAULT); i2c_imx->pinctrl_pins_gpio = pinctrl_lookup_state(i2c_imx->pinctrl, @@ -1001,12 +1014,15 @@ static void i2c_imx_init_recovery_info(struct imx_i2c_struct *i2c_imx, rinfo->scl_gpio = of_get_named_gpio_flags(pdev->dev.of_node, "scl-gpios", 0, NULL); - if (!gpio_is_valid(rinfo->sda_gpio) || - !gpio_is_valid(rinfo->scl_gpio) || - IS_ERR(i2c_imx->pinctrl_pins_default) || - IS_ERR(i2c_imx->pinctrl_pins_gpio)) { + if (rinfo->sda_gpio == -EPROBE_DEFER || + rinfo->scl_gpio == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (!gpio_is_valid(rinfo->sda_gpio) || + !gpio_is_valid(rinfo->scl_gpio) || + IS_ERR(i2c_imx->pinctrl_pins_default) || + IS_ERR(i2c_imx->pinctrl_pins_gpio)) { dev_dbg(&pdev->dev, "recovery information incomplete\n"); - return; + return 0; } dev_dbg(&pdev->dev, "using scl-gpio %d and sda-gpio %d for recovery\n", @@ -1016,6 +1032,8 @@ static void i2c_imx_init_recovery_info(struct imx_i2c_struct *i2c_imx, rinfo->unprepare_recovery = i2c_imx_unprepare_recovery; rinfo->recover_bus = i2c_generic_gpio_recovery; i2c_imx->adapter.bus_recovery_info = rinfo; + + return 0; } static u32 i2c_imx_func(struct i2c_adapter *adapter) @@ -1086,12 +1104,6 @@ static int i2c_imx_probe(struct platform_device *pdev) return ret; } - i2c_imx->pinctrl = devm_pinctrl_get(&pdev->dev); - if (IS_ERR(i2c_imx->pinctrl)) { - ret = PTR_ERR(i2c_imx->pinctrl); - goto clk_disable; - } - /* Request IRQ */ ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr, 0, pdev->name, i2c_imx); @@ -1118,7 +1130,11 @@ static int i2c_imx_probe(struct platform_device *pdev) i2c_imx, IMX_I2C_I2CR); imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR); - i2c_imx_init_recovery_info(i2c_imx, pdev); + /* Init optional bus recovery function */ + ret = i2c_imx_init_recovery_info(i2c_imx, pdev); + /* Give it another chance if pinctrl used is not ready yet */ + if (ret == -EPROBE_DEFER) + goto clk_disable; /* Add I2C adapter */ ret = i2c_add_numbered_adapter(&i2c_imx->adapter); diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index e701e28fb1cd..75e97081994f 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -196,4 +196,14 @@ config MCP4922 To compile this driver as a module, choose M here: the module will be called mcp4922. +config VF610_DAC + tristate "Vybrid vf610 DAC driver" + depends on OF + depends on HAS_IOMEM + help + Say yes here to support Vybrid board digital-to-analog converter. + + This driver can also be built as a module. If so, the module will + be called vf610_dac. + endmenu diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 63ae05633e0c..93feb2717671 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -21,3 +21,4 @@ obj-$(CONFIG_MAX517) += max517.o obj-$(CONFIG_MAX5821) += max5821.o obj-$(CONFIG_MCP4725) += mcp4725.o obj-$(CONFIG_MCP4922) += mcp4922.o +obj-$(CONFIG_VF610_DAC) += vf610_dac.o diff --git a/drivers/iio/dac/vf610_dac.c b/drivers/iio/dac/vf610_dac.c new file mode 100644 index 000000000000..c4ec7779b394 --- /dev/null +++ b/drivers/iio/dac/vf610_dac.c @@ -0,0 +1,298 @@ +/* + * Freescale Vybrid vf610 DAC driver + * + * Copyright 2016 Toradex AG + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define VF610_DACx_STATCTRL 0x20 + +#define VF610_DAC_DACEN BIT(15) +#define VF610_DAC_DACRFS BIT(14) +#define VF610_DAC_LPEN BIT(11) + +#define VF610_DAC_DAT0(x) ((x) & 0xFFF) + +enum vf610_conversion_mode_sel { + VF610_DAC_CONV_HIGH_POWER, + VF610_DAC_CONV_LOW_POWER, +}; + +struct vf610_dac { + struct clk *clk; + struct device *dev; + enum vf610_conversion_mode_sel conv_mode; + void __iomem *regs; +}; + +static void vf610_dac_init(struct vf610_dac *info) +{ + int val; + + info->conv_mode = VF610_DAC_CONV_LOW_POWER; + val = VF610_DAC_DACEN | VF610_DAC_DACRFS | + VF610_DAC_LPEN; + writel(val, info->regs + VF610_DACx_STATCTRL); +} + +static void vf610_dac_exit(struct vf610_dac *info) +{ + int val; + + val = readl(info->regs + VF610_DACx_STATCTRL); + val &= ~VF610_DAC_DACEN; + writel(val, info->regs + VF610_DACx_STATCTRL); +} + +static int vf610_set_conversion_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct vf610_dac *info = iio_priv(indio_dev); + int val; + + mutex_lock(&indio_dev->mlock); + info->conv_mode = mode; + val = readl(info->regs + VF610_DACx_STATCTRL); + if (mode) + val |= VF610_DAC_LPEN; + else + val &= ~VF610_DAC_LPEN; + writel(val, info->regs + VF610_DACx_STATCTRL); + mutex_unlock(&indio_dev->mlock); + + return 0; +} + +static int vf610_get_conversion_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct vf610_dac *info = iio_priv(indio_dev); + + return info->conv_mode; +} + +static const char * const vf610_conv_modes[] = { "high-power", "low-power" }; + +static const struct iio_enum vf610_conversion_mode = { + .items = vf610_conv_modes, + .num_items = ARRAY_SIZE(vf610_conv_modes), + .get = vf610_get_conversion_mode, + .set = vf610_set_conversion_mode, +}; + +static const struct iio_chan_spec_ext_info vf610_ext_info[] = { + IIO_ENUM("conversion_mode", IIO_SHARED_BY_DIR, + &vf610_conversion_mode), + {}, +}; + +#define VF610_DAC_CHAN(_chan_type) { \ + .type = (_chan_type), \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = vf610_ext_info, \ +} + +static const struct iio_chan_spec vf610_dac_iio_channels[] = { + VF610_DAC_CHAN(IIO_VOLTAGE), +}; + +static int vf610_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct vf610_dac *info = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = VF610_DAC_DAT0(readl(info->regs)); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* + * DACRFS is always 1 for valid reference and typical + * reference voltage as per Vybrid datasheet is 3.3V + * from section 9.1.2.1 of Vybrid datasheet + */ + *val = 3300 /* mV */; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static int vf610_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, + long mask) +{ + struct vf610_dac *info = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + writel(VF610_DAC_DAT0(val), info->regs); + mutex_unlock(&indio_dev->mlock); + return 0; + + default: + return -EINVAL; + } +} + +static const struct iio_info vf610_dac_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = &vf610_read_raw, + .write_raw = &vf610_write_raw, +}; + +static const struct of_device_id vf610_dac_match[] = { + { .compatible = "fsl,vf610-dac", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vf610_dac_match); + +static int vf610_dac_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct vf610_dac *info; + struct resource *mem; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(struct vf610_dac)); + if (!indio_dev) { + dev_err(&pdev->dev, "Failed allocating iio device\n"); + return -ENOMEM; + } + + info = iio_priv(indio_dev); + info->dev = &pdev->dev; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + info->clk = devm_clk_get(&pdev->dev, "dac"); + if (IS_ERR(info->clk)) { + dev_err(&pdev->dev, "Failed getting clock, err = %ld\n", + PTR_ERR(info->clk)); + return PTR_ERR(info->clk); + } + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = &pdev->dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->info = &vf610_dac_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = vf610_dac_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(vf610_dac_iio_channels); + + ret = clk_prepare_enable(info->clk); + if (ret) { + dev_err(&pdev->dev, + "Could not prepare or enable the clock\n"); + return ret; + } + + vf610_dac_init(info); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "Couldn't register the device\n"); + goto error_iio_device_register; + } + + return 0; + +error_iio_device_register: + clk_disable_unprepare(info->clk); + + return ret; +} + +static int vf610_dac_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct vf610_dac *info = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + vf610_dac_exit(info); + clk_disable_unprepare(info->clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int vf610_dac_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct vf610_dac *info = iio_priv(indio_dev); + + vf610_dac_exit(info); + clk_disable_unprepare(info->clk); + + return 0; +} + +static int vf610_dac_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct vf610_dac *info = iio_priv(indio_dev); + int ret; + + ret = clk_prepare_enable(info->clk); + if (ret) + return ret; + + vf610_dac_init(info); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(vf610_dac_pm_ops, vf610_dac_suspend, vf610_dac_resume); + +static struct platform_driver vf610_dac_driver = { + .probe = vf610_dac_probe, + .remove = vf610_dac_remove, + .driver = { + .name = "vf610-dac", + .of_match_table = vf610_dac_match, + .pm = &vf610_dac_pm_ops, + }, +}; +module_platform_driver(vf610_dac_driver); + +MODULE_AUTHOR("Sanchayan Maity <sanchayan.maity@toradex.com>"); +MODULE_DESCRIPTION("Freescale VF610 DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index ae33da7ab51f..637c5a7d8efc 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -115,6 +115,14 @@ config TOUCHSCREEN_ATMEL_MXT To compile this driver as a module, choose M here: the module will be called atmel_mxt_ts. +config TOUCHSCREEN_ATMEL_MXT_T37 + bool "Support T37 Diagnostic Data" + depends on TOUCHSCREEN_ATMEL_MXT && VIDEO_V4L2 + select VIDEOBUF2_VMALLOC + help + Say Y here if you want support to output data from the T37 + Diagnostic Data object using a V4L device. + config TOUCHSCREEN_AUO_PIXCIR tristate "AUO in-cell touchscreen using Pixcir ICs" depends on I2C @@ -619,6 +627,13 @@ config TOUCHSCREEN_MIGOR To compile this driver as a module, choose M here: the module will be called migor_ts. +config TOUCHSCREEN_FUSION_F0710A + tristate "TouchRevolution Fusion F0710A Touchscreens" + depends on I2C + help + Say Y here if you want to support the multi-touch input driver for + the TouchRevolution Fusion 7 and 10 panels. + config TOUCHSCREEN_TOUCHRIGHT tristate "Touchright serial touchscreen" select SERIO diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index cbaa6abb08da..4ae9f5c8f6f2 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -91,3 +91,4 @@ obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o obj-$(CONFIG_TOUCHSCREEN_COLIBRI_VF50) += colibri-vf50-ts.o obj-$(CONFIG_TOUCHSCREEN_ROHM_BU21023) += rohm_bu21023.o +obj-$(CONFIG_TOUCHSCREEN_FUSION_F0710A) += fusion_F0710A.o diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c index be2f2521c1c5..cb99b192769b 100644 --- a/drivers/input/touchscreen/atmel_mxt_ts.c +++ b/drivers/input/touchscreen/atmel_mxt_ts.c @@ -4,6 +4,7 @@ * Copyright (C) 2010 Samsung Electronics Co.Ltd * Copyright (C) 2011-2014 Atmel Corporation * Copyright (C) 2012 Google, Inc. + * Copyright (C) 2016 Zodiac Inflight Innovations * * Author: Joonyoung Shim <jy0922.shim@samsung.com> * @@ -27,7 +28,12 @@ #include <linux/interrupt.h> #include <linux/of.h> #include <linux/slab.h> +#include <linux/gpio/consumer.h> #include <asm/unaligned.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-v4l2.h> +#include <media/videobuf2-vmalloc.h> /* Firmware files */ #define MXT_FW_NAME "maxtouch.fw" @@ -99,6 +105,8 @@ struct t7_config { /* MXT_TOUCH_MULTI_T9 field */ #define MXT_T9_CTRL 0 +#define MXT_T9_XSIZE 3 +#define MXT_T9_YSIZE 4 #define MXT_T9_ORIENT 9 #define MXT_T9_RANGE 18 @@ -113,17 +121,37 @@ struct t7_config { #define MXT_T9_DETECT (1 << 7) struct t9_range { - u16 x; - u16 y; + __le16 x; + __le16 y; } __packed; /* MXT_TOUCH_MULTI_T9 orient */ #define MXT_T9_ORIENT_SWITCH (1 << 0) +#define MXT_T9_ORIENT_INVERTX (1 << 1) +#define MXT_T9_ORIENT_INVERTY (1 << 2) /* MXT_SPT_COMMSCONFIG_T18 */ #define MXT_COMMS_CTRL 0 #define MXT_COMMS_CMD 1 +/* MXT_DEBUG_DIAGNOSTIC_T37 */ +#define MXT_DIAGNOSTIC_PAGEUP 0x01 +#define MXT_DIAGNOSTIC_DELTAS 0x10 +#define MXT_DIAGNOSTIC_REFS 0x11 +#define MXT_DIAGNOSTIC_SIZE 128 + +#define MXT_FAMILY_1386 160 +#define MXT1386_COLUMNS 3 +#define MXT1386_PAGES_PER_COLUMN 8 + +struct t37_debug { +#ifdef CONFIG_TOUCHSCREEN_ATMEL_MXT_T37 + u8 mode; + u8 page; + u8 data[MXT_DIAGNOSTIC_SIZE]; +#endif +}; + /* Define for MXT_GEN_COMMAND_T6 */ #define MXT_BOOT_VALUE 0xa5 #define MXT_RESET_VALUE 0x01 @@ -133,10 +161,14 @@ struct t9_range { #define MXT_T100_CTRL 0 #define MXT_T100_CFG1 1 #define MXT_T100_TCHAUX 3 +#define MXT_T100_XSIZE 9 #define MXT_T100_XRANGE 13 +#define MXT_T100_YSIZE 20 #define MXT_T100_YRANGE 24 #define MXT_T100_CFG_SWITCHXY BIT(5) +#define MXT_T100_CFG_INVERTY BIT(6) +#define MXT_T100_CFG_INVERTX BIT(7) #define MXT_T100_TCHAUX_VECT BIT(0) #define MXT_T100_TCHAUX_AMPL BIT(1) @@ -161,6 +193,8 @@ enum t100_type { /* Delay times */ #define MXT_BACKUP_TIME 50 /* msec */ +#define MXT_RESET_GPIO_TIME 20 /* msec */ +#define MXT_RESET_INVALID_CHG 100 /* msec */ #define MXT_RESET_TIME 200 /* msec */ #define MXT_RESET_TIMEOUT 3000 /* msec */ #define MXT_CRC_TIMEOUT 1000 /* msec */ @@ -205,6 +239,37 @@ struct mxt_object { u8 num_report_ids; } __packed; +struct mxt_dbg { + u16 t37_address; + u16 diag_cmd_address; + struct t37_debug *t37_buf; + unsigned int t37_pages; + unsigned int t37_nodes; + + struct v4l2_device v4l2; + struct v4l2_pix_format format; + struct video_device vdev; + struct vb2_queue queue; + struct mutex lock; + int input; +}; + +enum v4l_dbg_inputs { + MXT_V4L_INPUT_DELTAS, + MXT_V4L_INPUT_REFS, + MXT_V4L_INPUT_MAX, +}; + +static const struct v4l2_file_operations mxt_video_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .unlocked_ioctl = video_ioctl2, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + /* Each client has this additional data */ struct mxt_data { struct i2c_client *client; @@ -216,6 +281,11 @@ struct mxt_data { unsigned int irq; unsigned int max_x; unsigned int max_y; + bool invertx; + bool inverty; + bool xy_switch; + u8 xsize; + u8 ysize; bool in_bootloader; u16 mem_size; u8 t100_aux_ampl; @@ -232,6 +302,8 @@ struct mxt_data { u8 num_touchids; u8 multitouch; struct t7_config t7_cfg; + struct mxt_dbg dbg; + struct gpio_desc *reset_gpio; /* Cached parameters from object table */ u16 T5_address; @@ -256,6 +328,11 @@ struct mxt_data { struct completion crc_completion; }; +struct mxt_vb2_buffer { + struct vb2_buffer vb; + struct list_head list; +}; + static size_t mxt_obj_size(const struct mxt_object *obj) { return obj->size_minus_one + 1; @@ -277,6 +354,7 @@ static bool mxt_object_readable(unsigned int type) case MXT_TOUCH_KEYARRAY_T15: case MXT_TOUCH_PROXIMITY_T23: case MXT_TOUCH_PROXKEY_T52: + case MXT_TOUCH_MULTITOUCHSCREEN_T100: case MXT_PROCI_GRIPFACE_T20: case MXT_PROCG_NOISE_T22: case MXT_PROCI_ONETOUCH_T24: @@ -1092,6 +1170,19 @@ static int mxt_t6_command(struct mxt_data *data, u16 cmd_offset, return 0; } +static int mxt_acquire_irq(struct mxt_data *data) +{ + int error; + + enable_irq(data->irq); + + error = mxt_process_messages_until_invalid(data); + if (error) + return error; + + return 0; +} + static int mxt_soft_reset(struct mxt_data *data) { struct device *dev = &data->client->dev; @@ -1108,9 +1199,9 @@ static int mxt_soft_reset(struct mxt_data *data) return ret; /* Ignore CHG line for 100ms after reset */ - msleep(100); + msleep(MXT_RESET_INVALID_CHG); - enable_irq(data->irq); + mxt_acquire_irq(data); ret = mxt_wait_for_completion(data, &data->reset_completion, MXT_RESET_TIMEOUT); @@ -1465,19 +1556,6 @@ release_mem: return ret; } -static int mxt_acquire_irq(struct mxt_data *data) -{ - int error; - - enable_irq(data->irq); - - error = mxt_process_messages_until_invalid(data); - if (error) - return error; - - return 0; -} - static int mxt_get_info(struct mxt_data *data) { struct i2c_client *client = data->client; @@ -1502,6 +1580,11 @@ static void mxt_free_input_device(struct mxt_data *data) static void mxt_free_object_table(struct mxt_data *data) { +#ifdef CONFIG_TOUCHSCREEN_ATMEL_MXT_T37 + video_unregister_device(&data->dbg.vdev); + v4l2_device_unregister(&data->dbg.v4l2); +#endif + kfree(data->object_table); data->object_table = NULL; kfree(data->msg_buf); @@ -1661,13 +1744,25 @@ static int mxt_read_t9_resolution(struct mxt_data *data) return -EINVAL; error = __mxt_read_reg(client, + object->start_address + MXT_T9_XSIZE, + sizeof(data->xsize), &data->xsize); + if (error) + return error; + + error = __mxt_read_reg(client, + object->start_address + MXT_T9_YSIZE, + sizeof(data->ysize), &data->ysize); + if (error) + return error; + + error = __mxt_read_reg(client, object->start_address + MXT_T9_RANGE, sizeof(range), &range); if (error) return error; - le16_to_cpus(&range.x); - le16_to_cpus(&range.y); + data->max_x = get_unaligned_le16(&range.x); + data->max_y = get_unaligned_le16(&range.y); error = __mxt_read_reg(client, object->start_address + MXT_T9_ORIENT, @@ -1675,23 +1770,9 @@ static int mxt_read_t9_resolution(struct mxt_data *data) if (error) return error; - /* Handle default values */ - if (range.x == 0) - range.x = 1023; - - if (range.y == 0) - range.y = 1023; - - if (orient & MXT_T9_ORIENT_SWITCH) { - data->max_x = range.y; - data->max_y = range.x; - } else { - data->max_x = range.x; - data->max_y = range.y; - } - - dev_dbg(&client->dev, - "Touchscreen size X%uY%u\n", data->max_x, data->max_y); + data->xy_switch = orient & MXT_T9_ORIENT_SWITCH; + data->invertx = orient & MXT_T9_ORIENT_INVERTX; + data->inverty = orient & MXT_T9_ORIENT_INVERTY; return 0; } @@ -1709,13 +1790,14 @@ static int mxt_read_t100_config(struct mxt_data *data) if (!object) return -EINVAL; + /* read touchscreen dimensions */ error = __mxt_read_reg(client, object->start_address + MXT_T100_XRANGE, sizeof(range_x), &range_x); if (error) return error; - le16_to_cpus(&range_x); + data->max_x = get_unaligned_le16(&range_x); error = __mxt_read_reg(client, object->start_address + MXT_T100_YRANGE, @@ -1723,36 +1805,38 @@ static int mxt_read_t100_config(struct mxt_data *data) if (error) return error; - le16_to_cpus(&range_y); + data->max_y = get_unaligned_le16(&range_y); + + error = __mxt_read_reg(client, + object->start_address + MXT_T100_XSIZE, + sizeof(data->xsize), &data->xsize); + if (error) + return error; + + error = __mxt_read_reg(client, + object->start_address + MXT_T100_YSIZE, + sizeof(data->ysize), &data->ysize); + if (error) + return error; + /* read orientation config */ error = __mxt_read_reg(client, object->start_address + MXT_T100_CFG1, 1, &cfg); if (error) return error; + data->xy_switch = cfg & MXT_T100_CFG_SWITCHXY; + data->invertx = cfg & MXT_T100_CFG_INVERTX; + data->inverty = cfg & MXT_T100_CFG_INVERTY; + + /* allocate aux bytes */ error = __mxt_read_reg(client, object->start_address + MXT_T100_TCHAUX, 1, &tchaux); if (error) return error; - /* Handle default values */ - if (range_x == 0) - range_x = 1023; - - if (range_y == 0) - range_y = 1023; - - if (cfg & MXT_T100_CFG_SWITCHXY) { - data->max_x = range_y; - data->max_y = range_x; - } else { - data->max_x = range_x; - data->max_y = range_y; - } - - /* allocate aux bytes */ aux = 6; if (tchaux & MXT_T100_TCHAUX_VECT) @@ -1768,9 +1852,6 @@ static int mxt_read_t100_config(struct mxt_data *data) "T100 aux mappings vect:%u ampl:%u area:%u\n", data->t100_aux_vect, data->t100_aux_ampl, data->t100_aux_area); - dev_info(&client->dev, - "T100 Touchscreen size X%uY%u\n", data->max_x, data->max_y); - return 0; } @@ -1829,6 +1910,19 @@ static int mxt_initialize_input_device(struct mxt_data *data) return -EINVAL; } + /* Handle default values and orientation switch */ + if (data->max_x == 0) + data->max_x = 1023; + + if (data->max_y == 0) + data->max_y = 1023; + + if (data->xy_switch) + swap(data->max_x, data->max_y); + + dev_info(dev, "Touchscreen size X%uY%u\n", data->max_x, data->max_y); + + /* Register input device */ input_dev = input_allocate_device(); if (!input_dev) { dev_err(dev, "Failed to allocate memory\n"); @@ -2060,6 +2154,420 @@ recheck: return 0; } +#ifdef CONFIG_TOUCHSCREEN_ATMEL_MXT_T37 +static u16 mxt_get_debug_value(struct mxt_data *data, unsigned int x, + unsigned int y) +{ + struct mxt_info *info = &data->info; + struct mxt_dbg *dbg = &data->dbg; + unsigned int ofs, page; + unsigned int col = 0; + unsigned int col_width; + + if (info->family_id == MXT_FAMILY_1386) { + col_width = info->matrix_ysize / MXT1386_COLUMNS; + col = y / col_width; + y = y % col_width; + } else { + col_width = info->matrix_ysize; + } + + ofs = (y + (x * col_width)) * sizeof(u16); + page = ofs / MXT_DIAGNOSTIC_SIZE; + ofs %= MXT_DIAGNOSTIC_SIZE; + + if (info->family_id == MXT_FAMILY_1386) + page += col * MXT1386_PAGES_PER_COLUMN; + + return get_unaligned_le16(&dbg->t37_buf[page].data[ofs]); +} + +static int mxt_convert_debug_pages(struct mxt_data *data, u16 *outbuf) +{ + struct mxt_dbg *dbg = &data->dbg; + unsigned int x = 0; + unsigned int y = 0; + unsigned int i, rx, ry; + + for (i = 0; i < dbg->t37_nodes; i++) { + /* Handle orientation */ + rx = data->xy_switch ? y : x; + ry = data->xy_switch ? x : y; + rx = data->invertx ? (data->xsize - 1 - rx) : rx; + ry = data->inverty ? (data->ysize - 1 - ry) : ry; + + outbuf[i] = mxt_get_debug_value(data, rx, ry); + + /* Next value */ + if (++x >= (data->xy_switch ? data->ysize : data->xsize)) { + x = 0; + y++; + } + } + + return 0; +} + +static int mxt_read_diagnostic_debug(struct mxt_data *data, u8 mode, + u16 *outbuf) +{ + struct mxt_dbg *dbg = &data->dbg; + int retries = 0; + int page; + int ret; + u8 cmd = mode; + struct t37_debug *p; + u8 cmd_poll; + + for (page = 0; page < dbg->t37_pages; page++) { + p = dbg->t37_buf + page; + + ret = mxt_write_reg(data->client, dbg->diag_cmd_address, + cmd); + if (ret) + return ret; + + retries = 0; + msleep(20); +wait_cmd: + /* Read back command byte */ + ret = __mxt_read_reg(data->client, dbg->diag_cmd_address, + sizeof(cmd_poll), &cmd_poll); + if (ret) + return ret; + + /* Field is cleared once the command has been processed */ + if (cmd_poll) { + if (retries++ > 100) + return -EINVAL; + + msleep(20); + goto wait_cmd; + } + + /* Read T37 page */ + ret = __mxt_read_reg(data->client, dbg->t37_address, + sizeof(struct t37_debug), p); + if (ret) + return ret; + + if (p->mode != mode || p->page != page) { + dev_err(&data->client->dev, "T37 page mismatch\n"); + return -EINVAL; + } + + dev_dbg(&data->client->dev, "%s page:%d retries:%d\n", + __func__, page, retries); + + /* For remaining pages, write PAGEUP rather than mode */ + cmd = MXT_DIAGNOSTIC_PAGEUP; + } + + return mxt_convert_debug_pages(data, outbuf); +} + +static int mxt_queue_setup(struct vb2_queue *q, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct mxt_data *data = q->drv_priv; + size_t size = data->dbg.t37_nodes * sizeof(u16); + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + return 0; +} + +static void mxt_buffer_queue(struct vb2_buffer *vb) +{ + struct mxt_data *data = vb2_get_drv_priv(vb->vb2_queue); + u16 *ptr; + int ret; + u8 mode; + + ptr = vb2_plane_vaddr(vb, 0); + if (!ptr) { + dev_err(&data->client->dev, "Error acquiring frame ptr\n"); + goto fault; + } + + switch (data->dbg.input) { + case MXT_V4L_INPUT_DELTAS: + default: + mode = MXT_DIAGNOSTIC_DELTAS; + break; + + case MXT_V4L_INPUT_REFS: + mode = MXT_DIAGNOSTIC_REFS; + break; + } + + ret = mxt_read_diagnostic_debug(data, mode, ptr); + if (ret) + goto fault; + + vb2_set_plane_payload(vb, 0, data->dbg.t37_nodes * sizeof(u16)); + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + return; + +fault: + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); +} + +/* V4L2 structures */ +static const struct vb2_ops mxt_queue_ops = { + .queue_setup = mxt_queue_setup, + .buf_queue = mxt_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static const struct vb2_queue mxt_queue = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ, + .buf_struct_size = sizeof(struct mxt_vb2_buffer), + .ops = &mxt_queue_ops, + .mem_ops = &vb2_vmalloc_memops, + .timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC, + .min_buffers_needed = 1, +}; + +static int mxt_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct mxt_data *data = video_drvdata(file); + + strlcpy(cap->driver, "atmel_mxt_ts", sizeof(cap->driver)); + strlcpy(cap->card, "atmel_mxt_ts touch", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "I2C:%s", dev_name(&data->client->dev)); + return 0; +} + +static int mxt_vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index >= MXT_V4L_INPUT_MAX) + return -EINVAL; + + i->type = V4L2_INPUT_TYPE_TOUCH; + + switch (i->index) { + case MXT_V4L_INPUT_REFS: + strlcpy(i->name, "Mutual Capacitance References", + sizeof(i->name)); + break; + case MXT_V4L_INPUT_DELTAS: + strlcpy(i->name, "Mutual Capacitance Deltas", sizeof(i->name)); + break; + } + + return 0; +} + +static int mxt_set_input(struct mxt_data *data, unsigned int i) +{ + struct v4l2_pix_format *f = &data->dbg.format; + + if (i >= MXT_V4L_INPUT_MAX) + return -EINVAL; + + if (i == MXT_V4L_INPUT_DELTAS) + f->pixelformat = V4L2_TCH_FMT_DELTA_TD16; + else + f->pixelformat = V4L2_TCH_FMT_TU16; + + f->width = data->xy_switch ? data->ysize : data->xsize; + f->height = data->xy_switch ? data->xsize : data->ysize; + f->field = V4L2_FIELD_NONE; + f->colorspace = V4L2_COLORSPACE_RAW; + f->bytesperline = f->width * sizeof(u16); + f->sizeimage = f->width * f->height * sizeof(u16); + + data->dbg.input = i; + + return 0; +} + +static int mxt_vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + return mxt_set_input(video_drvdata(file), i); +} + +static int mxt_vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct mxt_data *data = video_drvdata(file); + + *i = data->dbg.input; + + return 0; +} + +static int mxt_vidioc_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mxt_data *data = video_drvdata(file); + + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + f->fmt.pix = data->dbg.format; + + return 0; +} + +static int mxt_vidioc_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (fmt->index) { + case 0: + fmt->pixelformat = V4L2_TCH_FMT_TU16; + break; + + case 1: + fmt->pixelformat = V4L2_TCH_FMT_DELTA_TD16; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int mxt_vidioc_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + a->parm.capture.readbuffers = 1; + a->parm.capture.timeperframe.numerator = 1; + a->parm.capture.timeperframe.denominator = 10; + return 0; +} + +static const struct v4l2_ioctl_ops mxt_video_ioctl_ops = { + .vidioc_querycap = mxt_vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = mxt_vidioc_enum_fmt, + .vidioc_s_fmt_vid_cap = mxt_vidioc_fmt, + .vidioc_g_fmt_vid_cap = mxt_vidioc_fmt, + .vidioc_try_fmt_vid_cap = mxt_vidioc_fmt, + .vidioc_g_parm = mxt_vidioc_g_parm, + + .vidioc_enum_input = mxt_vidioc_enum_input, + .vidioc_g_input = mxt_vidioc_g_input, + .vidioc_s_input = mxt_vidioc_s_input, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static const struct video_device mxt_video_device = { + .name = "Atmel maxTouch", + .fops = &mxt_video_fops, + .ioctl_ops = &mxt_video_ioctl_ops, + .release = video_device_release_empty, + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TOUCH | + V4L2_CAP_READWRITE | V4L2_CAP_STREAMING, +}; + +static void mxt_debug_init(struct mxt_data *data) +{ + struct mxt_info *info = &data->info; + struct mxt_dbg *dbg = &data->dbg; + struct mxt_object *object; + int error; + + object = mxt_get_object(data, MXT_GEN_COMMAND_T6); + if (!object) + goto error; + + dbg->diag_cmd_address = object->start_address + MXT_COMMAND_DIAGNOSTIC; + + object = mxt_get_object(data, MXT_DEBUG_DIAGNOSTIC_T37); + if (!object) + goto error; + + if (mxt_obj_size(object) != sizeof(struct t37_debug)) { + dev_warn(&data->client->dev, "Bad T37 size"); + goto error; + } + + dbg->t37_address = object->start_address; + + /* Calculate size of data and allocate buffer */ + dbg->t37_nodes = data->xsize * data->ysize; + + if (info->family_id == MXT_FAMILY_1386) + dbg->t37_pages = MXT1386_COLUMNS * MXT1386_PAGES_PER_COLUMN; + else + dbg->t37_pages = DIV_ROUND_UP(data->xsize * + info->matrix_ysize * + sizeof(u16), + sizeof(dbg->t37_buf->data)); + + dbg->t37_buf = devm_kmalloc_array(&data->client->dev, dbg->t37_pages, + sizeof(struct t37_debug), GFP_KERNEL); + if (!dbg->t37_buf) + goto error; + + /* init channel to zero */ + mxt_set_input(data, 0); + + /* register video device */ + snprintf(dbg->v4l2.name, sizeof(dbg->v4l2.name), "%s", "atmel_mxt_ts"); + error = v4l2_device_register(&data->client->dev, &dbg->v4l2); + if (error) + goto error; + + /* initialize the queue */ + mutex_init(&dbg->lock); + dbg->queue = mxt_queue; + dbg->queue.drv_priv = data; + dbg->queue.lock = &dbg->lock; + dbg->queue.dev = &data->client->dev; + + error = vb2_queue_init(&dbg->queue); + if (error) + goto error_unreg_v4l2; + + dbg->vdev = mxt_video_device; + dbg->vdev.v4l2_dev = &dbg->v4l2; + dbg->vdev.lock = &dbg->lock; + dbg->vdev.vfl_dir = VFL_DIR_RX; + dbg->vdev.queue = &dbg->queue; + video_set_drvdata(&dbg->vdev, data); + + error = video_register_device(&dbg->vdev, VFL_TYPE_TOUCH, -1); + if (error) + goto error_unreg_v4l2; + + return; + +error_unreg_v4l2: + v4l2_device_unregister(&dbg->v4l2); +error: + dev_warn(&data->client->dev, "Error initializing T37\n"); +} +#else +static void mxt_debug_init(struct mxt_data *data) +{ +} +#endif + static int mxt_configure_objects(struct mxt_data *data, const struct firmware *cfg) { @@ -2087,6 +2595,8 @@ static int mxt_configure_objects(struct mxt_data *data, dev_warn(dev, "No touch object detected\n"); } + mxt_debug_init(data); + dev_info(dev, "Family: %u Variant: %u Firmware V%u.%u.%02X Objects: %u\n", info->family_id, info->variant_id, info->version >> 4, @@ -2621,11 +3131,9 @@ static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id) if (IS_ERR(pdata)) return PTR_ERR(pdata); - data = kzalloc(sizeof(struct mxt_data), GFP_KERNEL); - if (!data) { - dev_err(&client->dev, "Failed to allocate memory\n"); + data = devm_kzalloc(&client->dev, sizeof(struct mxt_data), GFP_KERNEL); + if (!data) return -ENOMEM; - } snprintf(data->phys, sizeof(data->phys), "i2c-%u-%04x/input0", client->adapter->nr, client->addr); @@ -2639,19 +3147,34 @@ static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id) init_completion(&data->reset_completion); init_completion(&data->crc_completion); - error = request_threaded_irq(client->irq, NULL, mxt_interrupt, - pdata->irqflags | IRQF_ONESHOT, - client->name, data); + data->reset_gpio = devm_gpiod_get_optional(&client->dev, + "reset", GPIOD_OUT_LOW); + if (IS_ERR(data->reset_gpio)) { + error = PTR_ERR(data->reset_gpio); + dev_err(&client->dev, "Failed to get reset gpio: %d\n", error); + return error; + } + + error = devm_request_threaded_irq(&client->dev, client->irq, + NULL, mxt_interrupt, + pdata->irqflags | IRQF_ONESHOT, + client->name, data); if (error) { dev_err(&client->dev, "Failed to register interrupt\n"); - goto err_free_mem; + return error; } disable_irq(client->irq); + if (data->reset_gpio) { + msleep(MXT_RESET_GPIO_TIME); + gpiod_set_value(data->reset_gpio, 1); + msleep(MXT_RESET_INVALID_CHG); + } + error = mxt_initialize(data); if (error) - goto err_free_irq; + return error; error = sysfs_create_group(&client->dev.kobj, &mxt_attr_group); if (error) { @@ -2665,10 +3188,6 @@ static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id) err_free_object: mxt_free_input_device(data); mxt_free_object_table(data); -err_free_irq: - free_irq(client->irq, data); -err_free_mem: - kfree(data); return error; } @@ -2676,11 +3195,10 @@ static int mxt_remove(struct i2c_client *client) { struct mxt_data *data = i2c_get_clientdata(client); + disable_irq(data->irq); sysfs_remove_group(&client->dev.kobj, &mxt_attr_group); - free_irq(data->irq, data); mxt_free_input_device(data); mxt_free_object_table(data); - kfree(data); return 0; } diff --git a/drivers/input/touchscreen/colibri-vf50-ts.c b/drivers/input/touchscreen/colibri-vf50-ts.c index 5d4903a402cc..51e86f80cdba 100644 --- a/drivers/input/touchscreen/colibri-vf50-ts.c +++ b/drivers/input/touchscreen/colibri-vf50-ts.c @@ -100,6 +100,8 @@ static void vf50_ts_enable_touch_detection(struct vf50_touch_device *vf50_ts) /* Wait for the pull-up to be stable on high */ usleep_range(COLI_PULLUP_MIN_DELAY_US, COLI_PULLUP_MAX_DELAY_US); + + enable_irq(vf50_ts->pen_irq); } /* @@ -112,6 +114,8 @@ static irqreturn_t vf50_ts_irq_bh(int irq, void *private) int val_x, val_y, val_z1, val_z2, val_p = 0; bool discard_val_on_start = true; + disable_irq_nosync(vf50_ts->pen_irq); + /* Disable the touch detection plates */ gpiod_set_value(vf50_ts->gpio_ym, 0); @@ -202,7 +206,8 @@ static irqreturn_t vf50_ts_irq_bh(int irq, void *private) input_report_key(vf50_ts->ts_input, BTN_TOUCH, 0); input_sync(vf50_ts->ts_input); - vf50_ts_enable_touch_detection(vf50_ts); + if (!vf50_ts->stop_touchscreen) + vf50_ts_enable_touch_detection(vf50_ts); return IRQ_HANDLED; } @@ -353,6 +358,7 @@ static int vf50_ts_probe(struct platform_device *pdev) if (touchdev->pen_irq < 0) return touchdev->pen_irq; + touchdev->stop_touchscreen = true; error = devm_request_threaded_irq(dev, touchdev->pen_irq, NULL, vf50_ts_irq_bh, IRQF_ONESHOT, "vf50 touch", touchdev); diff --git a/drivers/input/touchscreen/fusion_F0710A.c b/drivers/input/touchscreen/fusion_F0710A.c new file mode 100644 index 000000000000..862725b1332c --- /dev/null +++ b/drivers/input/touchscreen/fusion_F0710A.c @@ -0,0 +1,507 @@ +/* + * "fusion_F0710A" touchscreen driver + * + * This program 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <asm/irq.h> +#include <linux/gpio.h> +#include <linux/input/fusion_F0710A.h> +#include <linux/input/mt.h> +#include <linux/slab.h> +#include <linux/of_gpio.h> + + +#include "fusion_F0710A.h" + +#define DRV_NAME "fusion_F0710A" +#define MAX_TOUCHES 2 + +static struct fusion_F0710A_data fusion_F0710A; + +static unsigned short normal_i2c[] = { fusion_F0710A_I2C_SLAVE_ADDR, I2C_CLIENT_END }; + +static int fusion_F0710A_write_u8(u8 addr, u8 data) +{ + return i2c_smbus_write_byte_data(fusion_F0710A.client, addr, data); +} + +static int fusion_F0710A_read_u8(u8 addr) +{ + return i2c_smbus_read_byte_data(fusion_F0710A.client, addr); +} + +static int fusion_F0710A_read_block(u8 addr, u8 len, u8 *data) +{ + u8 msgbuf0[1] = { addr }; + u16 slave = fusion_F0710A.client->addr; + u16 flags = fusion_F0710A.client->flags; + struct i2c_msg msg[2] = { { slave, flags, 1, msgbuf0 }, + { slave, flags | I2C_M_RD, len, data } + }; + + return i2c_transfer(fusion_F0710A.client->adapter, msg, ARRAY_SIZE(msg)); +} + +static int fusion_F0710A_register_input(void) +{ + int ret; + struct input_dev *dev; + + dev = fusion_F0710A.input = input_allocate_device(); + if (dev == NULL) + return -ENOMEM; + + dev->name = "fusion_F0710A"; + + set_bit(EV_KEY, dev->evbit); + set_bit(EV_ABS, dev->evbit); + set_bit(EV_SYN, dev->evbit); + set_bit(BTN_TOUCH, dev->keybit); + + input_mt_init_slots(dev, MAX_TOUCHES, 0); + input_set_abs_params(dev, ABS_MT_POSITION_X, 0, fusion_F0710A.info.xres-1, 0, 0); + input_set_abs_params(dev, ABS_MT_POSITION_Y, 0, fusion_F0710A.info.yres-1, 0, 0); + input_set_abs_params(dev, ABS_MT_PRESSURE, 0, 255, 0, 0); + + input_set_abs_params(dev, ABS_X, 0, fusion_F0710A.info.xres-1, 0, 0); + input_set_abs_params(dev, ABS_Y, 0, fusion_F0710A.info.yres-1, 0, 0); + input_set_abs_params(dev, ABS_PRESSURE, 0, 255, 0, 0); + + ret = input_register_device(dev); + if (ret < 0) + goto bail1; + + return 0; + +bail1: + input_free_device(dev); + return ret; +} + +static void fusion_F0710A_reset(void) +{ + /* Generate a 0 => 1 edge explicitly, and wait for startup... */ + gpio_set_value(fusion_F0710A.gpio_reset, 0); + msleep(10); + gpio_set_value(fusion_F0710A.gpio_reset, 1); + /* Wait for startup (up to 125ms according to datasheet) */ + msleep(125); +} + +#define WC_RETRY_COUNT 3 +static int fusion_F0710A_write_complete(void) +{ + int ret, i; + + for (i = 0; i < WC_RETRY_COUNT; i++) + { + ret = fusion_F0710A_write_u8(fusion_F0710A_SCAN_COMPLETE, 0); + if (!ret) + break; + + dev_warn(&fusion_F0710A.client->dev, + "Write complete failed(%d): %d. Resetting controller...\n", i, ret); + fusion_F0710A_reset(); + } + + return ret; +} + +#define DATA_START fusion_F0710A_DATA_INFO +#define DATA_END fusion_F0710A_SEC_TIDTS +#define DATA_LEN (DATA_END - DATA_START + 1) +#define DATA_OFF(x) ((x) - DATA_START) + +static int fusion_F0710A_read_sensor(void) +{ + int ret; + u8 data[DATA_LEN]; + +#define DATA(x) (data[DATA_OFF(x)]) + /* To ensure data coherency, read the sensor with a single transaction. */ + ret = fusion_F0710A_read_block(DATA_START, DATA_LEN, data); + if (ret < 0) { + dev_err(&fusion_F0710A.client->dev, + "Read block failed: %d\n", ret); + + return ret; + } + + fusion_F0710A.f_num = DATA(fusion_F0710A_DATA_INFO)&0x03; + + fusion_F0710A.y1 = DATA(fusion_F0710A_POS_X1_HI) << 8; + fusion_F0710A.y1 |= DATA(fusion_F0710A_POS_X1_LO); + fusion_F0710A.x1 = DATA(fusion_F0710A_POS_Y1_HI) << 8; + fusion_F0710A.x1 |= DATA(fusion_F0710A_POS_Y1_LO); + fusion_F0710A.z1 = DATA(fusion_F0710A_FIR_PRESS); + fusion_F0710A.tip1 = DATA(fusion_F0710A_FIR_TIDTS)&0x0f; + fusion_F0710A.tid1 = (DATA(fusion_F0710A_FIR_TIDTS)&0xf0)>>4; + + fusion_F0710A.y2 = DATA(fusion_F0710A_POS_X2_HI) << 8; + fusion_F0710A.y2 |= DATA(fusion_F0710A_POS_X2_LO); + fusion_F0710A.x2 = DATA(fusion_F0710A_POS_Y2_HI) << 8; + fusion_F0710A.x2 |= DATA(fusion_F0710A_POS_Y2_LO); + fusion_F0710A.z2 = DATA(fusion_F0710A_SEC_PRESS); + fusion_F0710A.tip2 = DATA(fusion_F0710A_SEC_TIDTS)&0x0f; + fusion_F0710A.tid2 =(DATA(fusion_F0710A_SEC_TIDTS)&0xf0)>>4; +#undef DATA + + return 0; +} + +#define val_cut_max(x, max, reverse) \ +do \ +{ \ + if(x > max) \ + x = max; \ + if(reverse) \ + x = (max) - (x); \ +} \ +while(0) + +static void fusion_F0710A_wq(struct work_struct *work) +{ + struct input_dev *dev = fusion_F0710A.input; + + if (fusion_F0710A_read_sensor() < 0) + goto restore_irq; + +#ifdef DEBUG + printk(KERN_DEBUG "tip1, tid1, x1, y1, z1 (%x,%x,%d,%d,%d); tip2, tid2, x2, y2, z2 (%x,%x,%d,%d,%d)\n", + fusion_F0710A.tip1, fusion_F0710A.tid1, fusion_F0710A.x1, fusion_F0710A.y1, fusion_F0710A.z1, + fusion_F0710A.tip2, fusion_F0710A.tid2, fusion_F0710A.x2, fusion_F0710A.y2, fusion_F0710A.z2); +#endif /* DEBUG */ + + val_cut_max(fusion_F0710A.x1, fusion_F0710A.info.xres-1, fusion_F0710A.info.xy_reverse); + val_cut_max(fusion_F0710A.y1, fusion_F0710A.info.yres-1, fusion_F0710A.info.xy_reverse); + val_cut_max(fusion_F0710A.x2, fusion_F0710A.info.xres-1, fusion_F0710A.info.xy_reverse); + val_cut_max(fusion_F0710A.y2, fusion_F0710A.info.yres-1, fusion_F0710A.info.xy_reverse); + + if (fusion_F0710A.tid1) { + input_mt_slot(dev, fusion_F0710A.tid1 - 1); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, fusion_F0710A.tip1); + if (fusion_F0710A.tip1) { + input_report_abs(dev, ABS_MT_POSITION_X, fusion_F0710A.x1); + input_report_abs(dev, ABS_MT_POSITION_Y, fusion_F0710A.y1); + input_report_abs(dev, ABS_MT_PRESSURE, fusion_F0710A.z1); + } + } + + if (fusion_F0710A.tid2) { + input_mt_slot(dev, fusion_F0710A.tid2 - 1); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, fusion_F0710A.tip2); + if (fusion_F0710A.tip2) { + input_report_abs(dev, ABS_MT_POSITION_X, fusion_F0710A.x2); + input_report_abs(dev, ABS_MT_POSITION_Y, fusion_F0710A.y2); + input_report_abs(dev, ABS_MT_PRESSURE, fusion_F0710A.z2); + } + } + + input_mt_report_pointer_emulation(dev, false); + input_sync(dev); + +restore_irq: + enable_irq(fusion_F0710A.client->irq); + + /* Clear fusion_F0710A interrupt */ + fusion_F0710A_write_complete(); +} + +static DECLARE_WORK(fusion_F0710A_work, fusion_F0710A_wq); + +static irqreturn_t fusion_F0710A_interrupt(int irq, void *dev_id) +{ + disable_irq_nosync(fusion_F0710A.client->irq); + + queue_work(fusion_F0710A.workq, &fusion_F0710A_work); + + return IRQ_HANDLED; +} + +const static u8* g_ver_product[4] = { + "10Z8", "70Z7", "43Z6", "" +}; + +static int of_fusion_F0710A_get_pins(struct device_node *np, + unsigned int *int_pin, unsigned int *reset_pin) +{ + if (of_gpio_count(np) < 2) + return -ENODEV; + + *int_pin = of_get_gpio(np, 0); + *reset_pin = of_get_gpio(np, 1); + + if (!gpio_is_valid(*int_pin) || !gpio_is_valid(*reset_pin)) { + pr_err("%s: invalid GPIO pins, int=%d/reset=%d\n", + np->full_name, *int_pin, *reset_pin); + return -ENODEV; + } + + return 0; +} + +static int fusion_F0710A_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + struct device_node *np = i2c->dev.of_node; + struct fusion_f0710a_init_data *pdata = i2c->dev.platform_data; + int ret; + u8 ver_product, ver_id; + u32 version; + + if (np != NULL) { + pdata = i2c->dev.platform_data = + devm_kzalloc(&i2c->dev, sizeof(*pdata), GFP_KERNEL); + if (pdata == NULL) { + dev_err(&i2c->dev, "No platform data for Fusion driver\n"); + return -ENODEV; + } + /* the dtb did the pinmuxing for us */ + pdata->pinmux_fusion_pins = NULL; + ret = of_fusion_F0710A_get_pins(i2c->dev.of_node, + &pdata->gpio_int, &pdata->gpio_reset); + if (ret) + return ret; + } else if (pdata == NULL) { + dev_err(&i2c->dev, "No platform data for Fusion driver\n"); + return -ENODEV; + } + + /* Request pinmuxing, if necessary */ + if (pdata->pinmux_fusion_pins != NULL) { + ret = pdata->pinmux_fusion_pins(); + if (ret < 0) { + dev_err(&i2c->dev, "muxing GPIOs failed\n"); + return -ENODEV; + } + } + + if ((gpio_request(pdata->gpio_int, "Fusion pen down interrupt") == 0) && + (gpio_direction_input(pdata->gpio_int) == 0)) { + gpio_export(pdata->gpio_int, 0); + } else { + dev_warn(&i2c->dev, "Could not obtain GPIO for Fusion pen down\n"); + return -ENODEV; + } + + if ((gpio_request(pdata->gpio_reset, "Fusion reset") == 0) && + (gpio_direction_output(pdata->gpio_reset, 1) == 0)) { + fusion_F0710A.gpio_reset = pdata->gpio_reset; + fusion_F0710A_reset(); + gpio_export(pdata->gpio_reset, 0); + } else { + dev_warn(&i2c->dev, "Could not obtain GPIO for Fusion reset\n"); + ret = -ENODEV; + goto bail0; + } + + /* Use Pen Down GPIO as sampling interrupt */ + i2c->irq = gpio_to_irq(pdata->gpio_int); + irq_set_irq_type(i2c->irq, IRQ_TYPE_LEVEL_HIGH); + + if (!i2c->irq) { + dev_err(&i2c->dev, "fusion_F0710A irq < 0 \n"); + ret = -ENOMEM; + goto bail1; + } + + /* Attach the I2C client */ + fusion_F0710A.client = i2c; + i2c_set_clientdata(i2c, &fusion_F0710A); + + dev_info(&i2c->dev, "Touchscreen registered with bus id (%d) with slave address 0x%x\n", + i2c_adapter_id(fusion_F0710A.client->adapter), fusion_F0710A.client->addr); + + /* Read out a lot of registers */ + ret = fusion_F0710A_read_u8(fusion_F0710A_VIESION_INFO_LO); + if (ret < 0) { + dev_err(&i2c->dev, "query failed: %d\n", ret); + goto bail1; + } + + ver_product = (((u8)ret) & 0xc0) >> 6; + version = (10 + ((((u32)ret)&0x30) >> 4)) * 100000; + version += (((u32)ret)&0xf) * 1000; + /* Read out a lot of registers */ + ret = fusion_F0710A_read_u8(fusion_F0710A_VIESION_INFO); + if (ret < 0) { + dev_err(&i2c->dev, "query failed: %d\n", ret); + goto bail1; + } + + ver_id = ((u8)(ret) & 0x6) >> 1; + version += ((((u32)ret) & 0xf8) >> 3) * 10; + version += (((u32)ret) & 0x1) + 1; /* 0 is build 1, 1 is build 2 */ + dev_info(&i2c->dev, "version product %s(%d)\n", g_ver_product[ver_product], ver_product); + dev_info(&i2c->dev, "version id %s(%d)\n", ver_id ? "1.4" : "1.0", ver_id); + dev_info(&i2c->dev, "version series (%d)\n", version); + + switch(ver_product) + { + case fusion_F0710A_VIESION_07: /* 7 inch */ + fusion_F0710A.info.xres = fusion_F0710A07_XMAX; + fusion_F0710A.info.yres = fusion_F0710A07_YMAX; + fusion_F0710A.info.xy_reverse = fusion_F0710A07_REV; + break; + case fusion_F0710A_VIESION_43: /* 4.3 inch */ + fusion_F0710A.info.xres = fusion_F0710A43_XMAX; + fusion_F0710A.info.yres = fusion_F0710A43_YMAX; + fusion_F0710A.info.xy_reverse = fusion_F0710A43_REV; + break; + default: /* fusion_F0710A_VIESION_10 10 inch */ + fusion_F0710A.info.xres = fusion_F0710A10_XMAX; + fusion_F0710A.info.yres = fusion_F0710A10_YMAX; + fusion_F0710A.info.xy_reverse = fusion_F0710A10_REV; + break; + } + + /* Register the input device. */ + ret = fusion_F0710A_register_input(); + if (ret < 0) { + dev_err(&i2c->dev, "can't register input: %d\n", ret); + goto bail1; + } + + /* Create a worker thread */ + fusion_F0710A.workq = create_singlethread_workqueue(DRV_NAME); + if (fusion_F0710A.workq == NULL) { + dev_err(&i2c->dev, "can't create work queue\n"); + ret = -ENOMEM; + goto bail2; + } + + /* Register for the interrupt and enable it. Our handler will + * start getting invoked after this call. + */ + ret = request_irq(i2c->irq, fusion_F0710A_interrupt, IRQF_TRIGGER_RISING, + i2c->name, &fusion_F0710A); + if (ret < 0) { + dev_err(&i2c->dev, "can't get irq %d: %d\n", i2c->irq, ret); + goto bail3; + } + + /* clear the irq first */ + ret = fusion_F0710A_write_u8(fusion_F0710A_SCAN_COMPLETE, 0); + if (ret < 0) { + dev_err(&i2c->dev, "Clear irq failed: %d\n", ret); + goto bail4; + } + + return 0; + +bail4: + free_irq(i2c->irq, &fusion_F0710A); + +bail3: + destroy_workqueue(fusion_F0710A.workq); + fusion_F0710A.workq = NULL; + +bail2: + input_unregister_device(fusion_F0710A.input); +bail1: + gpio_free(pdata->gpio_reset); +bail0: + gpio_free(pdata->gpio_int); + + return ret; +} + +static int __maybe_unused fusion_F0710A_suspend(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + disable_irq(i2c->irq); + flush_workqueue(fusion_F0710A.workq); + + return 0; +} + +static int __maybe_unused fusion_F0710A_resume(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + enable_irq(i2c->irq); + + return 0; +} + +static int fusion_F0710A_remove(struct i2c_client *i2c) +{ + struct fusion_f0710a_init_data *pdata = i2c->dev.platform_data; + + gpio_free(pdata->gpio_int); + gpio_free(pdata->gpio_reset); + destroy_workqueue(fusion_F0710A.workq); + free_irq(i2c->irq, &fusion_F0710A); + input_unregister_device(fusion_F0710A.input); + i2c_set_clientdata(i2c, NULL); + + dev_info(&i2c->dev, "driver removed\n"); + + return 0; +} + +static struct i2c_device_id fusion_F0710A_id[] = { + {"fusion_F0710A", 0}, + {}, +}; + +static const struct of_device_id fusion_F0710A_dt_ids[] = { + { + .compatible = "touchrevolution,fusion-f0710a", + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, fusion_F0710A_dt_ids); + +static const struct dev_pm_ops fusion_F0710A_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(fusion_F0710A_suspend, fusion_F0710A_resume) +}; + +static struct i2c_driver fusion_F0710A_i2c_drv = { + .driver = { + .owner = THIS_MODULE, + .name = DRV_NAME, + .pm = &fusion_F0710A_pm_ops, + .of_match_table = fusion_F0710A_dt_ids, + }, + .probe = fusion_F0710A_probe, + .remove = fusion_F0710A_remove, + .id_table = fusion_F0710A_id, + .address_list = normal_i2c, +}; + +static int __init fusion_F0710A_init( void ) +{ + int ret; + + memset(&fusion_F0710A, 0, sizeof(fusion_F0710A)); + + /* Probe for fusion_F0710A on I2C. */ + ret = i2c_add_driver(&fusion_F0710A_i2c_drv); + if (ret < 0) { + printk(KERN_WARNING DRV_NAME " can't add i2c driver: %d\n", ret); + } + + return ret; +} + +static void __exit fusion_F0710A_exit( void ) +{ + i2c_del_driver(&fusion_F0710A_i2c_drv); +} + +module_init(fusion_F0710A_init); +module_exit(fusion_F0710A_exit); + +MODULE_DESCRIPTION("fusion_F0710A Touchscreen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/fusion_F0710A.h b/drivers/input/touchscreen/fusion_F0710A.h new file mode 100644 index 000000000000..6805332d23e5 --- /dev/null +++ b/drivers/input/touchscreen/fusion_F0710A.h @@ -0,0 +1,87 @@ +/* + * "fusion_F0710A" touchscreen driver + * + * This program 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. + */ + +/* I2C slave address */ +#define fusion_F0710A_I2C_SLAVE_ADDR 0x10 + +/* I2C registers */ +#define fusion_F0710A_DATA_INFO 0x00 + +/* First Point*/ +#define fusion_F0710A_POS_X1_HI 0x01 /* 16-bit register, MSB */ +#define fusion_F0710A_POS_X1_LO 0x02 /* 16-bit register, LSB */ +#define fusion_F0710A_POS_Y1_HI 0x03 /* 16-bit register, MSB */ +#define fusion_F0710A_POS_Y1_LO 0x04 /* 16-bit register, LSB */ +#define fusion_F0710A_FIR_PRESS 0X05 +#define fusion_F0710A_FIR_TIDTS 0X06 + +/* Second Point */ +#define fusion_F0710A_POS_X2_HI 0x07 /* 16-bit register, MSB */ +#define fusion_F0710A_POS_X2_LO 0x08 /* 16-bit register, LSB */ +#define fusion_F0710A_POS_Y2_HI 0x09 /* 16-bit register, MSB */ +#define fusion_F0710A_POS_Y2_LO 0x0A /* 16-bit register, LSB */ +#define fusion_F0710A_SEC_PRESS 0x0B +#define fusion_F0710A_SEC_TIDTS 0x0C + +#define fusion_F0710A_VIESION_INFO_LO 0X0E +#define fusion_F0710A_VIESION_INFO 0X0F + +#define fusion_F0710A_RESET 0x10 +#define fusion_F0710A_SCAN_COMPLETE 0x11 + + +#define fusion_F0710A_VIESION_10 0 +#define fusion_F0710A_VIESION_07 1 +#define fusion_F0710A_VIESION_43 2 + +/* fusion_F0710A 10 inch panel */ +#define fusion_F0710A10_XMAX 2275 +#define fusion_F0710A10_YMAX 1275 +#define fusion_F0710A10_REV 1 + +/* fusion_F0710A 7 inch panel */ +#define fusion_F0710A07_XMAX 1500 +#define fusion_F0710A07_YMAX 900 +#define fusion_F0710A07_REV 0 + +/* fusion_F0710A 4.3 inch panel */ +#define fusion_F0710A43_XMAX 900 +#define fusion_F0710A43_YMAX 500 +#define fusion_F0710A43_REV 0 + +#define fusion_F0710A_SAVE_PT1 0x1 +#define fusion_F0710A_SAVE_PT2 0x2 + + + +/* fusion_F0710A touch screen information */ +struct fusion_F0710A_info { + int xres; /* x resolution */ + int yres; /* y resolution */ + int xy_reverse; /* if need reverse in the x,y value x=xres-1-x, y=yres-1-y*/ +}; + +struct fusion_F0710A_data { + struct fusion_F0710A_info info; + struct i2c_client *client; + struct workqueue_struct *workq; + struct input_dev *input; + int gpio_reset; + u16 x1; + u16 y1; + u8 z1; + u8 tip1; + u8 tid1; + u16 x2; + u16 y2; + u8 z2; + u8 tip2; + u8 tid2; + u8 f_num; + u8 save_points; +}; diff --git a/drivers/input/touchscreen/ili210x.c b/drivers/input/touchscreen/ili210x.c index ddf694b9fffc..a235ed9f9bc8 100644 --- a/drivers/input/touchscreen/ili210x.c +++ b/drivers/input/touchscreen/ili210x.c @@ -280,7 +280,7 @@ static int ili210x_i2c_probe(struct i2c_client *client, goto err_remove_sysfs; } - device_init_wakeup(&client->dev, 1); + device_init_wakeup(dev, 1); dev_dbg(dev, "ILI210x initialized (IRQ: %d), firmware version %d.%d.%d", diff --git a/drivers/input/touchscreen/pixcir_i2c_ts.c b/drivers/input/touchscreen/pixcir_i2c_ts.c index 4b961ad9f0b5..bb91336bcae8 100644 --- a/drivers/input/touchscreen/pixcir_i2c_ts.c +++ b/drivers/input/touchscreen/pixcir_i2c_ts.c @@ -465,7 +465,7 @@ static int pixcir_i2c_ts_probe(struct i2c_client *client, if (error) return error; } else { - dev_err(&client->dev, "platform data not defined\n"); + dev_err(dev, "platform data not defined\n"); return -EINVAL; } @@ -487,7 +487,7 @@ static int pixcir_i2c_ts_probe(struct i2c_client *client, input->id.bustype = BUS_I2C; input->open = pixcir_input_open; input->close = pixcir_input_close; - input->dev.parent = &client->dev; + input->dev.parent = dev; if (pdata) { input_set_abs_params(input, ABS_MT_POSITION_X, 0, pdata->x_max, 0, 0); diff --git a/drivers/input/touchscreen/rohm_bu21023.c b/drivers/input/touchscreen/rohm_bu21023.c index ba6024f93469..9546da7cafe2 100644 --- a/drivers/input/touchscreen/rohm_bu21023.c +++ b/drivers/input/touchscreen/rohm_bu21023.c @@ -1189,8 +1189,7 @@ static int rohm_bu21023_i2c_probe(struct i2c_client *client, error = devm_add_action(dev, rohm_ts_remove_sysfs_group, dev); if (error) { rohm_ts_remove_sysfs_group(dev); - dev_err(&client->dev, - "Failed to add sysfs cleanup action: %d\n", + dev_err(dev, "Failed to add sysfs cleanup action: %d\n", error); return error; } diff --git a/drivers/input/touchscreen/s3c2410_ts.c b/drivers/input/touchscreen/s3c2410_ts.c index a4a103e1d11b..41d58e88cc8a 100644 --- a/drivers/input/touchscreen/s3c2410_ts.c +++ b/drivers/input/touchscreen/s3c2410_ts.c @@ -250,7 +250,7 @@ static int s3c2410ts_probe(struct platform_device *pdev) ts.dev = dev; - info = dev_get_platdata(&pdev->dev); + info = dev_get_platdata(dev); if (!info) { dev_err(dev, "no platform data, cannot attach\n"); return -EINVAL; diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 177f78f6e6d6..b0a94c3fc75e 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -42,6 +42,7 @@ obj-$(CONFIG_TB10X_IRQC) += irq-tb10x.o obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o +obj-$(CONFIG_SOC_VF610) += irq-vf610-gpc.o obj-$(CONFIG_SOC_VF610) += irq-vf610-mscm-ir.o obj-$(CONFIG_BCM7038_L1_IRQ) += irq-bcm7038-l1.o obj-$(CONFIG_BCM7120_L2_IRQ) += irq-bcm7120-l2.o diff --git a/drivers/irqchip/irq-vf610-gpc.c b/drivers/irqchip/irq-vf610-gpc.c new file mode 100644 index 000000000000..105a6606e425 --- /dev/null +++ b/drivers/irqchip/irq-vf610-gpc.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2015 Toradex AG + * Author: Stefan Agner <stefan@agner.ch> + * + * This program 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. + * + * + * The GPC (General Power Controller) irqchip driver takes care of the + * interrupt wakeup functionality. + * + * o All peripheral interrupts of the Vybrid SoC can be used as wakeup + * source from STOP mode. In LPSTOP mode however, the GPC is unpowered + * too and cannot be used to as a wakeup source. The WKPU (Wakeup Unit) + * is responsible for wakeups from LPSTOP modes. + */ + +#include <linux/cpu_pm.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/irqdomain.h> +#include <linux/mfd/syscon.h> +#include <dt-bindings/interrupt-controller/arm-gic.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/regmap.h> + +#define IMR_NUM 4 +#define VF610_GPC_IMR1 0x044 +#define VF610_GPC_MAX_IRQS (IMR_NUM * 32) + +static void __iomem *gpc_base; + +static int vf610_gpc_irq_set_wake(struct irq_data *d, unsigned int on) +{ + unsigned int idx = d->hwirq / 32; + void __iomem *reg_imr = gpc_base + VF610_GPC_IMR1 + (idx * 4); + u32 mask = 1 << d->hwirq % 32; + + if (on) + writel_relaxed(readl_relaxed(reg_imr) & ~mask, reg_imr); + else + writel_relaxed(readl_relaxed(reg_imr) | mask, reg_imr); + + /* + * Do *not* call into the parent, as the GIC doesn't have any + * wake-up facility... + */ + return 0; +} + +static struct irq_chip vf610_gpc_chip = { + .name = "vf610-gpc", + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_enable = irq_chip_enable_parent, + .irq_disable = irq_chip_disable_parent, + .irq_eoi = irq_chip_eoi_parent, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .irq_set_wake = vf610_gpc_irq_set_wake, +}; + +static int vf610_gpc_domain_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *arg) +{ + int i; + irq_hw_number_t hwirq; + struct irq_fwspec *fwspec = arg; + struct irq_fwspec parent_fwspec; + + if (!irq_domain_get_of_node(domain->parent)) + return -EINVAL; + + if (fwspec->param_count != 2) + return -EINVAL; + + hwirq = fwspec->param[0]; + for (i = 0; i < nr_irqs; i++) + irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, + &vf610_gpc_chip, NULL); + + parent_fwspec = *fwspec; + parent_fwspec.fwnode = domain->parent->fwnode; + return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, + &parent_fwspec); +} + +static int vf610_gpc_domain_translate(struct irq_domain *d, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) +{ + if (WARN_ON(fwspec->param_count < 2)) + return -EINVAL; + *hwirq = fwspec->param[0]; + *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; + return 0; +} + +static const struct irq_domain_ops gpc_irq_domain_ops = { + .translate = vf610_gpc_domain_translate, + .alloc = vf610_gpc_domain_alloc, + .free = irq_domain_free_irqs_common, +}; + +static int __init vf610_gpc_of_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_domain *domain, *domain_parent; + int i; + + domain_parent = irq_find_host(parent); + if (!domain_parent) { + pr_err("vf610_gpc: interrupt-parent not found\n"); + return -EINVAL; + } + + gpc_base = of_io_request_and_map(node, 0, "gpc"); + if (WARN_ON(!gpc_base)) + return -ENOMEM; + + domain = irq_domain_add_hierarchy(domain_parent, 0, VF610_GPC_MAX_IRQS, + node, &gpc_irq_domain_ops, NULL); + if (!domain) { + iounmap(gpc_base); + return -ENOMEM; + } + + /* Initially mask all interrupts for wakeup */ + for (i = 0; i < IMR_NUM; i++) + writel_relaxed(~0, gpc_base + VF610_GPC_IMR1 + i * 4); + + return 0; +} +IRQCHIP_DECLARE(vf610_gpc, "fsl,vf610-gpc", vf610_gpc_of_init); diff --git a/drivers/irqchip/irq-vf610-mscm-ir.c b/drivers/irqchip/irq-vf610-mscm-ir.c index 56b5e3cb9de2..a36f2ea74185 100644 --- a/drivers/irqchip/irq-vf610-mscm-ir.c +++ b/drivers/irqchip/irq-vf610-mscm-ir.c @@ -23,8 +23,10 @@ * variants of Vybrid. */ +#include <linux/bitops.h> #include <linux/cpu_pm.h> #include <linux/io.h> +#include <linux/interrupt.h> #include <linux/irq.h> #include <linux/irqchip.h> #include <linux/irqdomain.h> @@ -32,25 +34,48 @@ #include <dt-bindings/interrupt-controller/arm-gic.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/of_irq.h> #include <linux/slab.h> #include <linux/regmap.h> #define MSCM_CPxNUM 0x4 +#define MSCM_IRCP0IR 0x0 +#define MSCM_IRCP1IR 0x4 +#define MSCM_IRCPnIR(n) ((n) * 0x4 + MSCM_IRCP0IR) +#define MSCM_IRCPnIR_INT(n) (0x1 << (n)) +#define MSCM_IRCPGIR 0x20 + +#define MSCM_INTID_MASK 0x3 +#define MSCM_INTID(n) ((n) & MSCM_INTID_MASK) +#define MSCM_CPUTL(n) (((n) == 0 ? 1 : 2) << 16) + #define MSCM_IRSPRC(n) (0x80 + 2 * (n)) #define MSCM_IRSPRC_CPEN_MASK 0x3 #define MSCM_IRSPRC_NUM 112 +#define MSCM_CPU2CPU_NUM 4 + struct vf610_mscm_ir_chip_data { void __iomem *mscm_ir_base; - u16 cpu_mask; + u16 cpu_id; u16 saved_irsprc[MSCM_IRSPRC_NUM]; bool is_nvic; + struct device_node *cpu2cpu_node; +}; + +struct mscm_cpu2cpu_irq_data { + int intid; + int irq; + irq_handler_t handler; + void *priv; }; static struct vf610_mscm_ir_chip_data *mscm_ir_data; +static struct mscm_cpu2cpu_irq_data cpu2cpu_irq_data[MSCM_CPU2CPU_NUM]; + static inline void vf610_mscm_ir_save(struct vf610_mscm_ir_chip_data *data) { int i; @@ -94,11 +119,8 @@ static void vf610_mscm_ir_enable(struct irq_data *data) u16 irsprc; irsprc = readw_relaxed(chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); - irsprc &= MSCM_IRSPRC_CPEN_MASK; - WARN_ON(irsprc & ~chip_data->cpu_mask); - - writew_relaxed(chip_data->cpu_mask, + writew_relaxed(irsprc | BIT(chip_data->cpu_id), chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); irq_chip_enable_parent(data); @@ -108,8 +130,11 @@ static void vf610_mscm_ir_disable(struct irq_data *data) { irq_hw_number_t hwirq = data->hwirq; struct vf610_mscm_ir_chip_data *chip_data = data->chip_data; + u16 irsprc; - writew_relaxed(0x0, chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); + irsprc = readw_relaxed(chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); + writew_relaxed(irsprc & ~BIT(chip_data->cpu_id), + chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); irq_chip_disable_parent(data); } @@ -179,6 +204,90 @@ static const struct irq_domain_ops mscm_irq_domain_ops = { .free = irq_domain_free_irqs_common, }; +static irqreturn_t mscm_cpu2cpu_irq_handler(int irq, void *dev_id) +{ + irqreturn_t ret; + struct mscm_cpu2cpu_irq_data *data = dev_id; + void __iomem *mscm_base = mscm_ir_data->mscm_ir_base; + int cpu_id = mscm_ir_data->cpu_id; + + + ret = data->handler(data->intid, data->priv); + if (ret == IRQ_HANDLED) + writel(MSCM_IRCPnIR_INT(data->intid), mscm_base + MSCM_IRCPnIR(cpu_id)); + + return ret; +} + +int mscm_request_cpu2cpu_irq(unsigned int intid, irq_handler_t handler, + const char *name, void *priv) +{ + int irq; + struct mscm_cpu2cpu_irq_data *data; + + if (intid >= MSCM_CPU2CPU_NUM) + return -EINVAL; + + irq = of_irq_get(mscm_ir_data->cpu2cpu_node, intid); + if (irq < 0) + return irq; + + data = &cpu2cpu_irq_data[intid]; + data->intid = intid; + data->irq = irq; + data->handler = handler; + data->priv = priv; + + return request_irq(irq, mscm_cpu2cpu_irq_handler, 0, name, data); +} +EXPORT_SYMBOL(mscm_request_cpu2cpu_irq); + +void mscm_free_cpu2cpu_irq(unsigned int intid, void *priv) +{ + struct mscm_cpu2cpu_irq_data *data; + + if (intid >= MSCM_CPU2CPU_NUM) + return; + + data = &cpu2cpu_irq_data[intid]; + + if (data->irq < 0) + return; + + free_irq(data->irq, data); +} +EXPORT_SYMBOL(mscm_free_cpu2cpu_irq); + +void mscm_trigger_cpu2cpu_irq(unsigned int intid, int cpuid) +{ + void __iomem *mscm_base = mscm_ir_data->mscm_ir_base; + + writel(MSCM_INTID(intid) | MSCM_CPUTL(cpuid), mscm_base + MSCM_IRCPGIR); +} +EXPORT_SYMBOL(mscm_trigger_cpu2cpu_irq); + +void mscm_enable_cpu2cpu_irq(unsigned int intid) +{ + struct mscm_cpu2cpu_irq_data *data = &cpu2cpu_irq_data[intid]; + + if (intid >= MSCM_CPU2CPU_NUM) + return; + + enable_irq(data->irq); +} +EXPORT_SYMBOL(mscm_enable_cpu2cpu_irq); + +void mscm_disable_cpu2cpu_irq(unsigned int intid) +{ + struct mscm_cpu2cpu_irq_data *data = &cpu2cpu_irq_data[intid]; + + if (intid >= MSCM_CPU2CPU_NUM) + return; + + disable_irq(data->irq); +} +EXPORT_SYMBOL(mscm_disable_cpu2cpu_irq); + static int __init vf610_mscm_ir_of_init(struct device_node *node, struct device_node *parent) { @@ -210,8 +319,7 @@ static int __init vf610_mscm_ir_of_init(struct device_node *node, goto out_unmap; } - regmap_read(mscm_cp_regmap, MSCM_CPxNUM, &cpuid); - mscm_ir_data->cpu_mask = 0x1 << cpuid; + mscm_ir_data->cpu_id = regmap_read(mscm_cp_regmap, MSCM_CPxNUM, &cpuid); domain = irq_domain_add_hierarchy(domain_parent, 0, MSCM_IRSPRC_NUM, node, @@ -227,6 +335,8 @@ static int __init vf610_mscm_ir_of_init(struct device_node *node, cpu_pm_register_notifier(&mscm_ir_notifier_block); + mscm_ir_data->cpu2cpu_node = of_get_child_by_name(node, "cpu2cpu"); + return 0; out_unmap: diff --git a/drivers/mfd/syscon.c b/drivers/mfd/syscon.c index 176bf0fa2685..34d71de1bc1c 100644 --- a/drivers/mfd/syscon.c +++ b/drivers/mfd/syscon.c @@ -116,6 +116,36 @@ struct regmap *syscon_node_to_regmap(struct device_node *np) } EXPORT_SYMBOL_GPL(syscon_node_to_regmap); +int syscon_regmap_read_from_offset(struct device_node *np, + const char *s, unsigned int *val) +{ + struct of_phandle_args pargs; + struct regmap *regmap; + int offset; + int ret; + + if (!np) + return -ENODEV; + + ret = of_parse_phandle_with_fixed_args(np, s, 1, 0, &pargs); + if (ret) + return ret; + + regmap = syscon_node_to_regmap(pargs.np); + if (IS_ERR(regmap)) { + of_node_put(pargs.np); + return PTR_ERR(regmap); + } + + offset = pargs.args[0]; + of_node_put(pargs.np); + + ret = regmap_read(regmap, offset, val); + + return ret; +} +EXPORT_SYMBOL_GPL(syscon_regmap_read_from_offset); + struct regmap *syscon_regmap_lookup_by_compatible(const char *s) { struct device_node *syscon_np; diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 8d838779fd1b..31e95f9c1c93 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -1004,6 +1004,10 @@ sdhci_esdhc_imx_probe_dt(struct platform_device *pdev, host->quirks2 |= SDHCI_QUIRK2_NO_1_8_V; } + if (!of_property_read_u32(np, "bus-width", &boarddata->max_bus_width) + && boarddata->max_bus_width == 1) + host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA; + /* call to generic mmc_of_parse to support additional capabilities */ ret = mmc_of_parse(host->mmc); if (ret) @@ -1293,7 +1297,7 @@ static int sdhci_esdhc_runtime_resume(struct device *dev) #endif static const struct dev_pm_ops sdhci_esdhc_pmops = { - SET_SYSTEM_SLEEP_PM_OPS(sdhci_pltfm_suspend, sdhci_pltfm_resume) + SET_SYSTEM_SLEEP_PM_OPS(sdhci_pltfm_rpm_suspend, sdhci_pltfm_rpm_resume) SET_RUNTIME_PM_OPS(sdhci_esdhc_runtime_suspend, sdhci_esdhc_runtime_resume, NULL) }; diff --git a/drivers/mmc/host/sdhci-pltfm.c b/drivers/mmc/host/sdhci-pltfm.c index 87fb5ea8ebe7..714ffb5a6929 100644 --- a/drivers/mmc/host/sdhci-pltfm.c +++ b/drivers/mmc/host/sdhci-pltfm.c @@ -31,6 +31,7 @@ #include <linux/err.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/pm_runtime.h> #ifdef CONFIG_PPC #include <asm/machdep.h> #endif @@ -252,6 +253,41 @@ const struct dev_pm_ops sdhci_pltfm_pmops = { .resume = sdhci_pltfm_resume, }; EXPORT_SYMBOL_GPL(sdhci_pltfm_pmops); + +int sdhci_pltfm_rpm_suspend(struct device *dev) +{ + int ret; + struct sdhci_host *host = dev_get_drvdata(dev); + + pm_runtime_get_sync(dev); + ret = sdhci_suspend_host(host); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + if (ret) + return ret; + + return pm_runtime_force_suspend(dev); +} +EXPORT_SYMBOL_GPL(sdhci_pltfm_rpm_suspend); + +int sdhci_pltfm_rpm_resume(struct device *dev) +{ + int ret; + struct sdhci_host *host = dev_get_drvdata(dev); + + ret = pm_runtime_force_resume(dev); + + if (ret) + return ret; + + pm_runtime_get_sync(dev); + ret = sdhci_resume_host(host); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} +EXPORT_SYMBOL_GPL(sdhci_pltfm_rpm_resume); #endif /* CONFIG_PM */ static int __init sdhci_pltfm_drv_init(void) diff --git a/drivers/mmc/host/sdhci-pltfm.h b/drivers/mmc/host/sdhci-pltfm.h index 04bc2481e5c3..ac5f6ea9b55f 100644 --- a/drivers/mmc/host/sdhci-pltfm.h +++ b/drivers/mmc/host/sdhci-pltfm.h @@ -114,6 +114,8 @@ static inline void *sdhci_pltfm_priv(struct sdhci_pltfm_host *host) extern int sdhci_pltfm_suspend(struct device *dev); extern int sdhci_pltfm_resume(struct device *dev); extern const struct dev_pm_ops sdhci_pltfm_pmops; +extern int sdhci_pltfm_rpm_suspend(struct device *dev); +extern int sdhci_pltfm_rpm_resume(struct device *dev); #define SDHCI_PLTFM_PMOPS (&sdhci_pltfm_pmops) #else #define SDHCI_PLTFM_PMOPS NULL diff --git a/drivers/net/ethernet/freescale/Kconfig b/drivers/net/ethernet/freescale/Kconfig index bee32a9d9876..c7df0aebb2b8 100644 --- a/drivers/net/ethernet/freescale/Kconfig +++ b/drivers/net/ethernet/freescale/Kconfig @@ -30,6 +30,15 @@ config FEC Say Y here if you want to use the built-in 10/100 Fast ethernet controller on some Motorola ColdFire and Freescale i.MX processors. +config FSL_L2_SWITCH + tristate "Ethernet switch controller (Freescale Vybrid platform)" + depends on (SOC_VF610) + help + Say Y here if you want to use the built-in ethernet switch + controller on Vybrid processors. + The Integrated Ethernet switch engine is compatible with + 10/100 MAC-NET core. + config FEC_MPC52xx tristate "FEC MPC52xx driver" depends on PPC_MPC52xx && PPC_BESTCOMM diff --git a/drivers/net/ethernet/freescale/Makefile b/drivers/net/ethernet/freescale/Makefile index 71debd1c18c9..d5d24d182919 100644 --- a/drivers/net/ethernet/freescale/Makefile +++ b/drivers/net/ethernet/freescale/Makefile @@ -3,6 +3,7 @@ # obj-$(CONFIG_FEC) += fec.o +obj-$(CONFIG_FSL_L2_SWITCH) += fsl_l2_switch.o fec-objs :=fec_main.o fec_ptp.o obj-$(CONFIG_FEC_MPC52xx) += fec_mpc52xx.o ifeq ($(CONFIG_FEC_MPC52xx_MDIO),y) diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c index ae8e4fc22e7b..145fbcdc0cdd 100644 --- a/drivers/net/ethernet/freescale/fec_main.c +++ b/drivers/net/ethernet/freescale/fec_main.c @@ -103,7 +103,9 @@ static struct platform_device_id fec_devtype[] = { FEC_QUIRK_HAS_RACC, }, { .name = "mvf600-fec", - .driver_data = FEC_QUIRK_ENET_MAC | FEC_QUIRK_HAS_RACC, + .driver_data = FEC_QUIRK_ENET_MAC | FEC_QUIRK_HAS_RACC | + FEC_QUIRK_HAS_BUFDESC_EX | FEC_QUIRK_HAS_VLAN | + FEC_QUIRK_HAS_CSUM, }, { .name = "imx6sx-fec", .driver_data = FEC_QUIRK_ENET_MAC | FEC_QUIRK_HAS_GBIT | @@ -170,6 +172,7 @@ MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address"); /* FEC receive acceleration */ #define FEC_RACC_IPDIS (1 << 1) #define FEC_RACC_PRODIS (1 << 2) +#define FEC_RACC_SHIFT16 BIT(7) #define FEC_RACC_OPTIONS (FEC_RACC_IPDIS | FEC_RACC_PRODIS) /* @@ -975,9 +978,11 @@ fec_restart(struct net_device *ndev) #if !defined(CONFIG_M5272) if (fep->quirks & FEC_QUIRK_HAS_RACC) { - /* set RX checksum */ val = readl(fep->hwp + FEC_RACC); + /* align IP header */ + val |= FEC_RACC_SHIFT16; if (fep->csum_flags & FLAG_RX_CSUM_ENABLED) + /* set RX checksum */ val |= FEC_RACC_OPTIONS; else val &= ~FEC_RACC_OPTIONS; @@ -1463,6 +1468,12 @@ fec_enet_rx_queue(struct net_device *ndev, int budget, u16 queue_id) prefetch(skb->data - NET_IP_ALIGN); skb_put(skb, pkt_len - 4); data = skb->data; + +#if !defined(CONFIG_M5272) + if (fep->quirks & FEC_QUIRK_HAS_RACC) + data = skb_pull_inline(skb, 2); +#endif + if (!is_copybreak && need_swap) swap_buffer(data, pkt_len); diff --git a/drivers/net/ethernet/freescale/fsl_l2_switch.c b/drivers/net/ethernet/freescale/fsl_l2_switch.c new file mode 100644 index 000000000000..af38259b65ae --- /dev/null +++ b/drivers/net/ethernet/freescale/fsl_l2_switch.c @@ -0,0 +1,912 @@ +/* + * L2 switch Controller (Ethernet switch) driver + * for Freescale M5441x and Vybrid. + * + * Copyright 2010-2012 Freescale Semiconductor, Inc. + * Alison Wang (b18965@freescale.com) + * Jason Jin (Jason.jin@freescale.com) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include <linux/bitops.h> +#include <linux/phy.h> +#include <linux/syscalls.h> +#include <linux/clk.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/of_net.h> + +#include "fsl_l2_switch.h" + +/* switch ports status */ +struct port_status ports_link_status; + +static unsigned char macaddr[ETH_ALEN]; +module_param_array(macaddr, byte, NULL, 0); +MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address"); + +static void switch_hw_init(struct net_device *dev) +{ + struct switch_enet_private *fep = netdev_priv(dev); + + /* Initialize MAC 0/1 */ + writel(FSL_FEC_RCR_MAX_FL(1522) | FSL_FEC_RCR_RMII_MODE + | FSL_FEC_RCR_PROM | FSL_FEC_RCR_MII_MODE + | FSL_FEC_RCR_MII_MODE, fep->enetbase + FSL_FEC_RCR0); + writel(FSL_FEC_RCR_MAX_FL(1522) | FSL_FEC_RCR_RMII_MODE + | FSL_FEC_RCR_PROM | FSL_FEC_RCR_MII_MODE + | FSL_FEC_RCR_MII_MODE, fep->enetbase + FSL_FEC_RCR1); + + writel(FSL_FEC_TCR_FDEN, fep->enetbase + FSL_FEC_TCR0); + writel(FSL_FEC_TCR_FDEN, fep->enetbase + FSL_FEC_TCR1); + + /* Set the station address for the ENET Adapter */ + writel(dev->dev_addr[3] | + dev->dev_addr[2] << 8 | + dev->dev_addr[1] << 16 | + dev->dev_addr[0] << 24, fep->enetbase + FSL_FEC_PALR0); + writel((dev->dev_addr[5] << 16) | + (dev->dev_addr[4] << 24), + fep->enetbase + FSL_FEC_PAUR0); + writel(dev->dev_addr[3] | + dev->dev_addr[2] << 8 | + dev->dev_addr[1] << 16 | + dev->dev_addr[0] << 24, fep->enetbase + FSL_FEC_PALR1); + writel((dev->dev_addr[5] << 16) | + (dev->dev_addr[4] << 24), + fep->enetbase + FSL_FEC_PAUR1); + + writel(FEC_ENET_TXF | FEC_ENET_RXF, fep->enetbase + FSL_FEC_EIMR0); + writel(FEC_ENET_TXF | FEC_ENET_RXF, fep->enetbase + FSL_FEC_EIMR1); + + writel(FSL_FEC_ECR_ETHER_EN | + (0x1 << 8), fep->enetbase + FSL_FEC_ECR0); + writel(FSL_FEC_ECR_ETHER_EN | + (0x1 << 8), fep->enetbase + FSL_FEC_ECR1); + udelay(20); +} + +/* Set a MAC change in hardware.*/ +static void switch_get_mac_address(struct net_device *dev) +{ + struct switch_enet_private *fep = netdev_priv(dev); + unsigned char *iap, tmpaddr[ETH_ALEN]; + + iap = macaddr; + + if (!is_valid_ether_addr(iap)) { + struct device_node *np = fep->pdev->dev.of_node; + + if (np) { + const char *mac = of_get_mac_address(np); + + if (mac) + iap = (unsigned char *) mac; + } + } + + if (!is_valid_ether_addr(iap)) { + *((__be32 *)&tmpaddr[0]) = + cpu_to_be32(readl(fep->enetbase + FSL_FEC_PALR0)); + *((__be16 *)&tmpaddr[4]) = + cpu_to_be16(readl(fep->enetbase + FSL_FEC_PAUR0) >> 16); + iap = &tmpaddr[0]; + } + + if (!is_valid_ether_addr(iap)) { + /* Report it and use a random ethernet address instead */ + netdev_err(dev, "Invalid MAC address: %pM\n", iap); + eth_hw_addr_random(dev); + netdev_info(dev, "Using random MAC address: %pM\n", + dev->dev_addr); + return; + } + + memcpy(dev->dev_addr, iap, ETH_ALEN); + + /* Adjust MAC if using macaddr */ + if (iap == macaddr) + dev->dev_addr[ETH_ALEN - 1] = + macaddr[ETH_ALEN - 1] + fep->dev_id; +} + +/* This function is called to start or restart the FEC during a link + * change. This only happens when switching between half and full + * duplex. + */ +static void switch_restart(struct net_device *dev, int duplex) +{ + struct switch_enet_private *fep; + int i; + + fep = netdev_priv(dev); + + /* Whack a reset. We should wait for this.*/ + writel(1, fep->enetbase + FSL_FEC_ECR0); + writel(1, fep->enetbase + FSL_FEC_ECR1); + udelay(10); + + writel(FSL_ESW_MODE_SW_RST, fep->membase + FEC_ESW_MODE); + udelay(10); + writel(FSL_ESW_MODE_STATRST, fep->membase + FEC_ESW_MODE); + writel(FSL_ESW_MODE_SW_EN, fep->membase + FEC_ESW_MODE); + + /* Enable transmit/receive on all ports */ + writel(0xffffffff, fep->membase + FEC_ESW_PER); + + /* Management port configuration, + * make port 0 as management port + */ + writel(0, fep->membase + FEC_ESW_BMPC); + + /* Clear any outstanding interrupt.*/ + writel(0xffffffff, fep->membase + FEC_ESW_ISR); + + switch_hw_init(dev); + + /* Set station address.*/ + switch_get_mac_address(dev); + + writel(0, fep->membase + FEC_ESW_IMR); + udelay(10); + + /* Set maximum receive buffer size. */ + writel(PKT_MAXBLR_SIZE, fep->membase + FEC_ESW_MRBR); + + /* Set receive and transmit descriptor base. */ + writel(fep->bd_dma, fep->membase + FEC_ESW_RDSR); + writel((unsigned long)fep->bd_dma + + sizeof(struct bufdesc) * RX_RING_SIZE, + fep->membase + FEC_ESW_TDSR); + + fep->cur_tx = fep->tx_bd_base; + fep->dirty_tx = fep->cur_tx; + fep->cur_rx = fep->rx_bd_base; + + /* Reset SKB transmit buffers. */ + fep->skb_cur = 0; + fep->skb_dirty = 0; + for (i = 0; i <= TX_RING_MOD_MASK; i++) { + if (fep->tx_skbuff[i] != NULL) { + dev_kfree_skb_any(fep->tx_skbuff[i]); + fep->tx_skbuff[i] = NULL; + } + } + + /* Clear any outstanding interrupt.*/ + writel(0xffffffff, fep->membase + FEC_ESW_ISR); + writel(FSL_ESW_IMR_RXF | FSL_ESW_IMR_TXF, fep->membase + FEC_ESW_IMR); +} + +static int switch_enet_clk_enable(struct net_device *ndev, bool enable) +{ + struct switch_enet_private *fep = netdev_priv(ndev); + int ret; + + if (enable) { + ret = clk_prepare_enable(fep->clk_esw); + if (ret) + goto failed_clk_esw; + + ret = clk_prepare_enable(fep->clk_enet); + if (ret) + goto failed_clk_enet; + + ret = clk_prepare_enable(fep->clk_enet0); + if (ret) + goto failed_clk_enet0; + + ret = clk_prepare_enable(fep->clk_enet1); + if (ret) + goto failed_clk_enet1; + } else { + clk_disable_unprepare(fep->clk_esw); + clk_disable_unprepare(fep->clk_enet); + clk_disable_unprepare(fep->clk_enet0); + clk_disable_unprepare(fep->clk_enet1); + } + + return 0; + +failed_clk_esw: + clk_disable_unprepare(fep->clk_esw); +failed_clk_enet: + clk_disable_unprepare(fep->clk_enet); +failed_clk_enet0: + clk_disable_unprepare(fep->clk_enet0); +failed_clk_enet1: + clk_disable_unprepare(fep->clk_enet1); + + return ret; +} + +static void switch_enet_free_buffers(struct net_device *ndev) +{ + struct switch_enet_private *fep = netdev_priv(ndev); + int i; + struct sk_buff *skb; + cbd_t *bdp; + + bdp = fep->rx_bd_base; + for (i = 0; i < RX_RING_SIZE; i++) { + skb = fep->rx_skbuff[i]; + + if (bdp->cbd_bufaddr) + dma_unmap_single(&fep->pdev->dev, bdp->cbd_bufaddr, + SWITCH_ENET_RX_FRSIZE, DMA_FROM_DEVICE); + if (skb) + dev_kfree_skb(skb); + bdp++; + } + + bdp = fep->tx_bd_base; + for (i = 0; i < TX_RING_SIZE; i++) + kfree(fep->tx_bounce[i]); +} + +static int switch_alloc_buffers(struct net_device *ndev) +{ + struct switch_enet_private *fep = netdev_priv(ndev); + int i; + struct sk_buff *skb; + cbd_t *bdp; + + bdp = fep->rx_bd_base; + for (i = 0; i < RX_RING_SIZE; i++) { + skb = dev_alloc_skb(SWITCH_ENET_RX_FRSIZE); + if (!skb) { + switch_enet_free_buffers(ndev); + return -ENOMEM; + } + fep->rx_skbuff[i] = skb; + + bdp->cbd_bufaddr = dma_map_single(&fep->pdev->dev, skb->data, + SWITCH_ENET_RX_FRSIZE, DMA_FROM_DEVICE); + bdp->cbd_sc = BD_ENET_RX_EMPTY; + + bdp++; + } + + /* Set the last buffer to wrap. */ + bdp--; + bdp->cbd_sc |= BD_SC_WRAP; + + bdp = fep->tx_bd_base; + for (i = 0; i < TX_RING_SIZE; i++) { + fep->tx_bounce[i] = kmalloc(SWITCH_ENET_TX_FRSIZE, GFP_KERNEL); + + bdp->cbd_sc = 0; + bdp->cbd_bufaddr = 0; + bdp++; + } + + /* Set the last buffer to wrap. */ + bdp--; + bdp->cbd_sc |= BD_SC_WRAP; + + return 0; +} + +static int switch_enet_open(struct net_device *dev) +{ + struct switch_enet_private *fep = netdev_priv(dev); + unsigned long tmp = 0; + + /* no phy, go full duplex, it's most likely a hub chip */ + switch_restart(dev, 1); + + /* if the fec open firstly, we need to do nothing + * otherwise, we need to restart the FEC + */ + if (fep->sequence_done == 0) + switch_restart(dev, 1); + else + fep->sequence_done = 0; + + writel(0x70007, fep->membase + FEC_ESW_PER); + writel(FSL_ESW_DBCR_P0 | FSL_ESW_DBCR_P1 | FSL_ESW_DBCR_P2, + fep->membase + FEC_ESW_DBCR); + writel(FSL_ESW_DMCR_P0 | FSL_ESW_DMCR_P1 | FSL_ESW_DMCR_P2, + fep->membase + FEC_ESW_DMCR); + + /* Disable port learning */ + tmp = readl(fep->membase + FEC_ESW_BKLR); + tmp &= ~FSL_ESW_BKLR_LDX; + writel(tmp, fep->membase + FEC_ESW_BKLR); + + netif_start_queue(dev); + + /* And last, enable the receive processing. */ + writel(FSL_ESW_RDAR_R_DES_ACTIVE, fep->membase + FEC_ESW_RDAR); + + fep->opened = 1; + + return 0; +} + +static int switch_enet_close(struct net_device *dev) +{ + struct switch_enet_private *fep = netdev_priv(dev); + + /* Don't know what to do yet. */ + fep->opened = 0; + netif_stop_queue(dev); + + return 0; +} + +static netdev_tx_t switch_enet_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct switch_enet_private *fep; + cbd_t *bdp; + unsigned short status; + unsigned long flags; + void *bufaddr; + + fep = netdev_priv(dev); + + spin_lock_irqsave(&fep->hw_lock, flags); + /* Fill in a Tx ring entry */ + bdp = fep->cur_tx; + + status = bdp->cbd_sc; + + /* Clear all of the status flags */ + status &= ~BD_ENET_TX_STATS; + + /* Set buffer length and buffer pointer. */ + bufaddr = skb->data; + bdp->cbd_datlen = skb->len; + + /* On some FEC implementations data must be aligned on + * 4-byte boundaries. Use bounce buffers to copy data + * and get it aligned. Ugh. + */ + if (((unsigned long)bufaddr) & 0xf) { + unsigned int index; + index = bdp - fep->tx_bd_base; + + memcpy(fep->tx_bounce[index], + (void *)skb->data, bdp->cbd_datlen); + bufaddr = fep->tx_bounce[index]; + } + + /* Save skb pointer. */ + fep->tx_skbuff[fep->skb_cur] = skb; + + dev->stats.tx_bytes += skb->len; + fep->skb_cur = (fep->skb_cur + 1) & TX_RING_MOD_MASK; + + /* Push the data cache so the CPM does not get stale memory + * data. + */ + bdp->cbd_bufaddr = dma_map_single(&fep->pdev->dev, bufaddr, + SWITCH_ENET_TX_FRSIZE, DMA_TO_DEVICE); + + /* Send it on its way. Tell FEC it's ready, interrupt when done, + * it's the last BD of the frame, and to put the CRC on the end. + */ + status |= (BD_ENET_TX_READY | BD_ENET_TX_INTR + | BD_ENET_TX_LAST | BD_ENET_TX_TC); + bdp->cbd_sc = status; + + dev->trans_start = jiffies; + + /* Trigger transmission start */ + writel(FSL_ESW_TDAR_X_DES_ACTIVE, fep->membase + FEC_ESW_TDAR); + + /* If this was the last BD in the ring, + * start at the beginning again. + */ + if (status & BD_ENET_TX_WRAP) + bdp = fep->tx_bd_base; + else + bdp++; + + if (bdp == fep->dirty_tx) { + fep->tx_full = 1; + netif_stop_queue(dev); + netdev_err(dev, "%s: net stop\n", __func__); + } + + fep->cur_tx = (cbd_t *)bdp; + + spin_unlock_irqrestore(&fep->hw_lock, flags); + + return NETDEV_TX_OK; +} + +static void switch_timeout(struct net_device *dev) +{ + struct switch_enet_private *fep = netdev_priv(dev); + + netdev_err(dev, "%s: transmit timed out.\n", dev->name); + dev->stats.tx_errors++; + switch_restart(dev, fep->full_duplex); + netif_wake_queue(dev); +} + +static const struct net_device_ops switch_netdev_ops = { + .ndo_open = switch_enet_open, + .ndo_stop = switch_enet_close, + .ndo_start_xmit = switch_enet_start_xmit, + .ndo_tx_timeout = switch_timeout, +}; + +/* Initialize the FEC Ethernet. */ +static int switch_enet_init(struct net_device *dev) +{ + struct switch_enet_private *fep = netdev_priv(dev); + cbd_t *cbd_base = NULL; + int ret = 0; + + /* Allocate memory for buffer descriptors. */ + cbd_base = dma_alloc_coherent(NULL, PAGE_SIZE, &fep->bd_dma, + GFP_KERNEL); + if (!cbd_base) { + return -ENOMEM; + } + + spin_lock_init(&fep->hw_lock); + + writel(FSL_ESW_MODE_SW_RST, fep->membase + FEC_ESW_MODE); + udelay(10); + writel(FSL_ESW_MODE_STATRST, fep->membase + FEC_ESW_MODE); + writel(FSL_ESW_MODE_SW_EN, fep->membase + FEC_ESW_MODE); + + /* Enable transmit/receive on all ports */ + writel(0xffffffff, fep->membase + FEC_ESW_PER); + + /* Management port configuration, + * make port 0 as management port + */ + writel(0, fep->membase + FEC_ESW_BMPC); + + /* Clear any outstanding interrupt.*/ + writel(0xffffffff, fep->membase + FEC_ESW_ISR); + writel(0, fep->membase + FEC_ESW_IMR); + udelay(100); + + /* Get the Ethernet address */ + switch_get_mac_address(dev); + + /* Set receive and transmit descriptor base. */ + fep->rx_bd_base = cbd_base; + fep->tx_bd_base = cbd_base + RX_RING_SIZE; + + dev->base_addr = (unsigned long)fep->membase; + + /* The FEC Ethernet specific entries in the device structure. */ + dev->watchdog_timeo = TX_TIMEOUT; + dev->netdev_ops = &switch_netdev_ops; + + fep->skb_cur = fep->skb_dirty = 0; + + ret = switch_alloc_buffers(dev); + if (ret) + goto err_enet_alloc; + + /* Set receive and transmit descriptor base */ + writel(fep->bd_dma, fep->membase + FEC_ESW_RDSR); + writel((unsigned long)fep->bd_dma + + sizeof(struct bufdesc) * RX_RING_SIZE, + fep->membase + FEC_ESW_TDSR); + + /* set mii */ + switch_hw_init(dev); + + /* Clear any outstanding interrupt. */ + writel(0xffffffff, fep->membase + FEC_ESW_ISR); + writel(FSL_ESW_IMR_RXF | FSL_ESW_IMR_TXF, fep->membase + FEC_ESW_IMR); + + fep->sequence_done = 1; + + return ret; + +err_enet_alloc: + switch_enet_clk_enable(dev, false); + + return ret; +} + +static void switch_enet_tx(struct net_device *dev) +{ + struct switch_enet_private *fep; + struct bufdesc *bdp; + unsigned short status; + struct sk_buff *skb; + unsigned long flags; + + fep = netdev_priv(dev); + spin_lock_irqsave(&fep->hw_lock, flags); + bdp = fep->dirty_tx; + + while (((status = bdp->cbd_sc) & BD_ENET_TX_READY) == 0) { + if (bdp == fep->cur_tx && fep->tx_full == 0) + break; + + if (bdp->cbd_bufaddr) + dma_unmap_single(&fep->pdev->dev, bdp->cbd_bufaddr, + SWITCH_ENET_TX_FRSIZE, DMA_TO_DEVICE); + bdp->cbd_bufaddr = 0; + + skb = fep->tx_skbuff[fep->skb_dirty]; + /* Check for errors. */ + if (status & (BD_ENET_TX_HB | BD_ENET_TX_LC | + BD_ENET_TX_RL | BD_ENET_TX_UN | + BD_ENET_TX_CSL)) { + dev->stats.tx_errors++; + if (status & BD_ENET_TX_HB) /* No heartbeat */ + dev->stats.tx_heartbeat_errors++; + if (status & BD_ENET_TX_LC) /* Late collision */ + dev->stats.tx_window_errors++; + if (status & BD_ENET_TX_RL) /* Retrans limit */ + dev->stats.tx_aborted_errors++; + if (status & BD_ENET_TX_UN) /* Underrun */ + dev->stats.tx_fifo_errors++; + if (status & BD_ENET_TX_CSL) /* Carrier lost */ + dev->stats.tx_carrier_errors++; + } else { + dev->stats.tx_packets++; + } + + /* Deferred means some collisions occurred during transmit, + * but we eventually sent the packet OK. + */ + if (status & BD_ENET_TX_DEF) + dev->stats.collisions++; + + /* Free the sk buffer associated with this last transmit. + */ + dev_kfree_skb_any(skb); + fep->tx_skbuff[fep->skb_dirty] = NULL; + fep->skb_dirty = (fep->skb_dirty + 1) & TX_RING_MOD_MASK; + + /* Update pointer to next buffer descriptor to be transmitted. + */ + if (status & BD_ENET_TX_WRAP) + bdp = fep->tx_bd_base; + else + bdp++; + + /* Since we have freed up a buffer, the ring is no longer + * full. + */ + if (fep->tx_full) { + fep->tx_full = 0; + netdev_err(dev, "%s: tx full is zero\n", __func__); + if (netif_queue_stopped(dev)) + netif_wake_queue(dev); + } + } + fep->dirty_tx = (cbd_t *)bdp; + spin_unlock_irqrestore(&fep->hw_lock, flags); +} + +/* During a receive, the cur_rx points to the current incoming buffer. + * When we update through the ring, if the next incoming buffer has + * not been given to the system, we just set the empty indicator, + * effectively tossing the packet. + */ +static void switch_enet_rx(struct net_device *dev) +{ + struct switch_enet_private *fep; + cbd_t *bdp; + unsigned short status; + struct sk_buff *skb; + ushort pkt_len; + __u8 *data; + unsigned long flags; + + fep = netdev_priv(dev); + + spin_lock_irqsave(&fep->hw_lock, flags); + /* First, grab all of the stats for the incoming packet. + * These get messed up if we get called due to a busy condition. + */ + bdp = fep->cur_rx; + + while (!((status = bdp->cbd_sc) & BD_ENET_RX_EMPTY)) { + /* Since we have allocated space to hold a complete frame, + * the last indicator should be set. + */ + if ((status & BD_ENET_RX_LAST) == 0) + netdev_err(dev, "SWITCH ENET: rcv is not +last\n"); + + if (!fep->opened) + goto rx_processing_done; + + /* Check for errors. */ + if (status & (BD_ENET_RX_LG | BD_ENET_RX_SH | BD_ENET_RX_NO | + BD_ENET_RX_CR | BD_ENET_RX_OV)) { + dev->stats.rx_errors++; + if (status & (BD_ENET_RX_LG | BD_ENET_RX_SH)) { + /* Frame too long or too short. */ + dev->stats.rx_length_errors++; + } + if (status & BD_ENET_RX_NO) /* Frame alignment */ + dev->stats.rx_frame_errors++; + if (status & BD_ENET_RX_CR) /* CRC Error */ + dev->stats.rx_crc_errors++; + if (status & BD_ENET_RX_OV) /* FIFO overrun */ + dev->stats.rx_fifo_errors++; + } + /* Report late collisions as a frame error. + * On this error, the BD is closed, but we don't know what we + * have in the buffer. So, just drop this frame on the floor. + */ + if (status & BD_ENET_RX_CL) { + dev->stats.rx_errors++; + dev->stats.rx_frame_errors++; + goto rx_processing_done; + } + /* Process the incoming frame */ + dev->stats.rx_packets++; + pkt_len = bdp->cbd_datlen; + dev->stats.rx_bytes += pkt_len; + data = (__u8 *)__va(bdp->cbd_bufaddr); + + if (bdp->cbd_bufaddr) + dma_unmap_single(&fep->pdev->dev, bdp->cbd_bufaddr, + SWITCH_ENET_TX_FRSIZE, DMA_FROM_DEVICE); + + /* This does 16 byte alignment, exactly what we need. + * The packet length includes FCS, but we don't want to + * include that when passing upstream as it messes up + * bridging applications. + */ + skb = dev_alloc_skb(pkt_len - 4 + NET_IP_ALIGN); + + if (!skb) + dev->stats.rx_dropped++; + + if (unlikely(!skb)) { + netdev_err(dev, + "%s:Memory squeeze, dropping packet.\n", + dev->name); + dev->stats.rx_dropped++; + } else { + skb_reserve(skb, NET_IP_ALIGN); + skb_put(skb, pkt_len - 4); /* Make room */ + skb_copy_to_linear_data(skb, data, pkt_len - 4); + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + } + + bdp->cbd_bufaddr = dma_map_single(&fep->pdev->dev, data, + SWITCH_ENET_TX_FRSIZE, DMA_FROM_DEVICE); +rx_processing_done: + + /* Clear the status flags for this buffer */ + status &= ~BD_ENET_RX_STATS; + + /* Mark the buffer empty */ + status |= BD_ENET_RX_EMPTY; + bdp->cbd_sc = status; + + /* Update BD pointer to next entry */ + if (status & BD_ENET_RX_WRAP) + bdp = fep->rx_bd_base; + else + bdp++; + + /* Doing this here will keep the FEC running while we process + * incoming frames. On a heavily loaded network, we should be + * able to keep up at the expense of system resources. + */ + writel(FSL_ESW_RDAR_R_DES_ACTIVE, fep->membase + FEC_ESW_RDAR); + } + fep->cur_rx = (cbd_t *)bdp; + + spin_unlock_irqrestore(&fep->hw_lock, flags); +} + +/* The interrupt handler */ +static irqreturn_t switch_enet_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct switch_enet_private *fep = netdev_priv(dev); + uint int_events; + irqreturn_t ret = IRQ_NONE; + + /* Get the interrupt events that caused us to be here. */ + do { + int_events = readl(fep->membase + FEC_ESW_ISR); + writel(int_events, fep->membase + FEC_ESW_ISR); + + /* Handle receive event in its own function. */ + + if (int_events & FSL_ESW_ISR_RXF) { + ret = IRQ_HANDLED; + switch_enet_rx(dev); + } + + if (int_events & FSL_ESW_ISR_TXF) { + ret = IRQ_HANDLED; + switch_enet_tx(dev); + } + + } while (int_events); + + return ret; +} + +static int eth_switch_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct switch_enet_private *fep; + + fep = netdev_priv(dev); + + unregister_netdev(dev); + + writel(0, fep->enetbase + FSL_FEC_ECR0); + writel(0, fep->enetbase + FSL_FEC_ECR1); + udelay(10); + + writel(FSL_ESW_MODE_SW_RST, fep->membase + FEC_ESW_MODE); + udelay(10); + writel(FSL_ESW_MODE_STATRST, fep->membase + FEC_ESW_MODE); + writel(0, fep->membase + FEC_ESW_MODE); + + /* Disable transmit/receive on all ports */ + writel(0, fep->membase + FEC_ESW_PER); + + clk_disable_unprepare(fep->clk_enet1); + clk_disable_unprepare(fep->clk_enet0); + clk_disable_unprepare(fep->clk_enet); + clk_disable_unprepare(fep->clk_esw); + + free_netdev(dev); + + return 0; +} + +static const struct of_device_id of_eth_switch_match[] = { + { .compatible = "fsl,eth-switch", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, of_eth_switch_match); + +/* TODO: suspend/resume related code */ + +static int eth_switch_probe(struct platform_device *pdev) +{ + struct net_device *ndev = NULL; + struct switch_enet_private *fep = NULL; + int irq = 0, ret = 0, err = 0; + struct resource *res = NULL; + const struct of_device_id *match; + static int dev_id; + + match = of_match_device(of_eth_switch_match, &pdev->dev); + if (!match) + return -EINVAL; + + pdev->id_entry = match->data; + + /* Initialize network device */ + ndev = alloc_etherdev(sizeof(struct switch_enet_private)); + if (!ndev) + return -ENOMEM; + + SET_NETDEV_DEV(ndev, &pdev->dev); + + fep = netdev_priv(ndev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + fep->membase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(fep->membase)) { + ret = PTR_ERR(fep->membase); + goto failed_ioremap; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + fep->enetbase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(fep->enetbase)) { + ret = PTR_ERR(fep->enetbase); + goto failed_ioremap; + } + + fep->pdev = pdev; + fep->dev_id = dev_id++; + + platform_set_drvdata(pdev, ndev); + + irq = platform_get_irq(pdev, 0); + ret = devm_request_irq(&pdev->dev, irq, switch_enet_interrupt, + 0, pdev->name, ndev); + if (ret) + return ret; + + fep->clk_esw = devm_clk_get(&pdev->dev, "esw"); + if (IS_ERR(fep->clk_esw)) { + ret = PTR_ERR(fep->clk_esw); + goto failed_clk; + } + + fep->clk_enet = devm_clk_get(&pdev->dev, "enet"); + if (IS_ERR(fep->clk_enet)) { + ret = PTR_ERR(fep->clk_enet); + goto failed_clk; + } + + fep->clk_enet0 = devm_clk_get(&pdev->dev, "enet0"); + if (IS_ERR(fep->clk_enet0)) { + ret = PTR_ERR(fep->clk_enet0); + goto failed_clk; + } + + fep->clk_enet1 = devm_clk_get(&pdev->dev, "enet1"); + if (IS_ERR(fep->clk_enet1)) { + ret = PTR_ERR(fep->clk_enet1); + goto failed_clk; + } + + switch_enet_clk_enable(ndev, true); + + err = switch_enet_init(ndev); + if (err) { + free_netdev(ndev); + platform_set_drvdata(pdev, NULL); + return -EIO; + } + + /* register network device */ + ret = register_netdev(ndev); + if (ret) + goto failed_register; + + netdev_info(ndev, "%s: Ethernet switch %pM\n", + ndev->name, ndev->dev_addr); + + return 0; + +failed_register: +failed_clk: +failed_ioremap: + free_netdev(ndev); + + return ret; +} + +static struct platform_driver eth_switch_driver = { + .probe = eth_switch_probe, + .remove = (eth_switch_remove), + .driver = { + .name = "eth-switch", + .owner = THIS_MODULE, + .of_match_table = of_eth_switch_match, + }, +}; + +static int __init fsl_l2_switch_init(void) +{ + return platform_driver_register(ð_switch_driver); +} + +static void __exit fsl_l2_switch_exit(void) +{ + platform_driver_unregister(ð_switch_driver); +} + +module_init(fsl_l2_switch_init); +module_exit(fsl_l2_switch_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/freescale/fsl_l2_switch.h b/drivers/net/ethernet/freescale/fsl_l2_switch.h new file mode 100644 index 000000000000..62b5f1207752 --- /dev/null +++ b/drivers/net/ethernet/freescale/fsl_l2_switch.h @@ -0,0 +1,776 @@ +/* + * Copyright 2010-2012 Freescale Semiconductor, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + */ + +#ifndef FSL_L2_SWITCH_H +#define FSL_L2_SWITCH_H + +/* Interrupt events/masks */ +#define FEC_ENET_HBERR BIT(31) /* Heartbeat error */ +#define FEC_ENET_BABR BIT(30) /* Babbling receiver */ +#define FEC_ENET_BABT BIT(29) /* Babbling transmitter */ +#define FEC_ENET_GRA BIT(28) /* Graceful stop complete */ +#define FEC_ENET_TXF BIT(27) /* Full frame transmitted */ +#define FEC_ENET_TXB BIT(26) /* A buffer was transmitted */ +#define FEC_ENET_RXF BIT(25) /* Full frame received */ +#define FEC_ENET_RXB BIT(24) /* A buffer was received */ +#define FEC_ENET_MII BIT(23) /* MII interrupt */ +#define FEC_ENET_EBERR BIT(22) /* SDMA bus error */ + +#define NMII 20 + +/* Make MII read/write commands for the FEC */ +#define mk_mii_read(REG) (0x60020000 | ((REG & 0x1f) << 18)) +#define mk_mii_write(REG, VAL) (0x50020000 | ((REG & 0x1f) << 18) | \ + (VAL & 0xffff)) +/* MII MMFR bits definition */ +#define ESW_MMFR_ST BIT(30) +#define ESW_MMFR_OP_READ (2 << 28) +#define ESW_MMFR_OP_WRITE BIT(28) +#define ESW_MMFR_PA(v) ((v & 0x1f) << 23) +#define ESW_MMFR_RA(v) ((v & 0x1f) << 18) +#define ESW_MMFR_TA (2 << 16) +#define ESW_MMFR_DATA(v) (v & 0xffff) + +#define ESW_MII_TIMEOUT 30 /* ms */ + +/* Transmitter timeout.*/ +#define TX_TIMEOUT (2 * HZ) + +/* The Switch stores dest/src/type, data, + * and checksum for receive packets. + */ +#define PKT_MAXBUF_SIZE 1518 +#define PKT_MINBUF_SIZE 64 +#define PKT_MAXBLR_SIZE 1520 + +/* The 5441x RX control register also contains maximum frame + * size bits. + */ +#define OPT_FRAME_SIZE (PKT_MAXBUF_SIZE << 16) + +/* The number of Tx and Rx buffers. These are allocated from the page + * pool. The code may assume these are power of two, so it it best + * to keep them that size. + * We don't need to allocate pages for the transmitter. We just use + * the skbuffer directly. + */ +#ifdef CONFIG_SWITCH_DMA_USE_SRAM +#define SWITCH_ENET_RX_PAGES 6 +#else +#define SWITCH_ENET_RX_PAGES 8 +#endif + +#define SWITCH_ENET_RX_FRSIZE 2048 +#define SWITCH_ENET_RX_FRPPG (PAGE_SIZE / SWITCH_ENET_RX_FRSIZE) +#define RX_RING_SIZE (SWITCH_ENET_RX_FRPPG * SWITCH_ENET_RX_PAGES) +#define SWITCH_ENET_TX_FRSIZE 2048 +#define SWITCH_ENET_TX_FRPPG (PAGE_SIZE / SWITCH_ENET_TX_FRSIZE) + +#ifdef CONFIG_SWITCH_DMA_USE_SRAM +#define TX_RING_SIZE 8 /* Must be power of two */ +#define TX_RING_MOD_MASK 7 /* for this to work */ +#else +#define TX_RING_SIZE 16 /* Must be power of two */ +#define TX_RING_MOD_MASK 15 /* for this to work */ +#endif + +#define SWITCH_EPORT_NUMBER 2 + +#if (((RX_RING_SIZE + TX_RING_SIZE) * 8) > PAGE_SIZE) +#error "L2SWITCH: descriptor ring size constants too large" +#endif + +/* memory-mapped register offset */ +#define FEC_ESW_REVISION 0x00 +#define FEC_ESW_SCRATCH 0x04 +#define FEC_ESW_PER 0x08 + +#define FEC_ESW_VLANV 0x10 +#define FEC_ESW_DBCR 0x14 +#define FEC_ESW_DMCR 0x18 +#define FEC_ESW_BKLR 0x1C +#define FEC_ESW_BMPC 0x20 +#define FEC_ESW_MODE 0x24 +#define FEC_ESW_VIMSEL 0x28 +#define FEC_ESW_VOMSEL 0x2C +#define FEC_ESW_VIMEN 0x30 +#define FEC_ESW_VID 0x34 + +#define FEC_ESW_MCR 0x40 +#define FEC_ESW_EGMAP 0x44 +#define FEC_ESW_INGMAP 0x48 +#define FEC_ESW_INGSAL 0x4C +#define FEC_ESW_INGSAH 0x50 +#define FEC_ESW_INGDAL 0x54 +#define FEC_ESW_INGDAH 0x58 +#define FEC_ESW_ENGSAL 0x5C +#define FEC_ESW_ENGSAH 0x60 +#define FEC_ESW_ENGDAL 0x64 +#define FEC_ESW_ENGDAH 0x68 +#define FEC_ESW_MCVAL 0x6C + +#define FEC_ESW_MMSR 0x80 +#define FEC_ESW_LMT 0x84 +#define FEC_ESW_LFC 0x88 +#define FEC_ESW_PCSR 0x8C +#define FEC_ESW_IOSR 0x90 +#define FEC_ESW_QWT 0x94 + +#define FEC_ESW_P0BCT 0x9C + +#define FEC_ESW_P0FFEN 0xBC +#define FEC_ESW_PSNP(n) (0xC0 + 4 * n) +#define FEC_ESW_IPSNP(n) (0xE0 + 4 * n) +#define FEC_ESW_PVRES(n) (0x100 + 4 * n) + +#define FEC_ESW_IPRES 0x140 + +/* port0-port2 Priority Configuration 0xFC0D_C180-C188 */ +#define FEC_ESW_PRES(n) (0x180 + n * 4) + +/* port0-port2 VLAN ID 0xFC0D_C200-C208 */ +#define FEC_ESW_PID(n) (0x200 + 4 * n) + +/* port0-port2 VLAN domain resolution entry 0xFC0D_C280-C2FC */ +#define FEC_ESW_VRES(n) (0x280 + n * 4) + +#define FEC_ESW_DISCN 0x300 +#define FEC_ESW_DISCB 0x304 +#define FEC_ESW_NDISCN 0x308 +#define FEC_ESW_NDISCB 0x30C +/* per port statistics 0xFC0DC310_C33C */ + +#define FEC_ESW_POQC(n) (0x310 + n * 16) +#define FEC_ESW_PMVID(n) (0x310 + n * 16 + 0x04) +#define FEC_ESW_PMVTAG(n) (0x310 + n * 16 + 0x08) +#define FEC_ESW_PBL(n) (0x310 + n * 16 + 0x0C) + +#define FEC_ESW_ISR 0x400 /* Interrupt event reg */ +#define FEC_ESW_IMR 0x404 /* Interrupt mask reg */ +#define FEC_ESW_RDSR 0x408 /* Receive descriptor ring */ +#define FEC_ESW_TDSR 0x40C /* Transmit descriptor ring */ +#define FEC_ESW_MRBR 0x410 /* Maximum receive buff size */ +#define FEC_ESW_RDAR 0x414 /* Receive descriptor active */ +#define FEC_ESW_TDAR 0x418 /* Transmit descriptor active */ + +#define FEC_ESW_LREC0 0x500 +#define FEC_ESW_LREC1 0x504 +#define FEC_ESW_LSR 0x508 + +#include <linux/phy.h> +struct switch_platform_data { + phy_interface_t phy; + unsigned char mac[ETH_ALEN]; +}; + +#define FSL_FEC_MMFR0 (0x40) +#define FSL_FEC_MSCR0 (0x44) +#define FSL_FEC_MSCR1 (0x1044) +#define FSL_FEC_RCR0 (0x84) +#define FSL_FEC_RCR1 (0x1084) +#define FSL_FEC_TCR0 (0xc4) +#define FSL_FEC_TCR1 (0x10c4) +#define FSL_FEC_ECR0 (0x24) +#define FSL_FEC_ECR1 (0x1024) +#define FSL_FEC_EIR0 (0x04) +#define FSL_FEC_EIR1 (0x1004) +#define FSL_FEC_EIMR0 (0x08) +#define FSL_FEC_EIMR1 (0x1008) +#define FSL_FEC_PALR0 (0x0e4) +#define FSL_FEC_PAUR0 (0x0e8) +#define FSL_FEC_PALR1 (0x10e4) +#define FSL_FEC_PAUR1 (0x10e8) +#define FSL_FEC_X_WMRK0 (0x0144) +#define FSL_FEC_X_WMRK1 (0x1144) + +#define FSL_FEC_RCR_MII_MODE (0x00000004) +#define FSL_FEC_RCR_PROM (0x00000008) +#define FSL_FEC_RCR_RMII_MODE (0x00000100) +#define FSL_FEC_RCR_MAX_FL(x) (((x) & 0x00003FFF) << 16) +#define FSL_FEC_RCR_CRC_FWD (0x00004000) + +#define FSL_FEC_TCR_FDEN (0x00000004) + +#define FSL_FEC_ECR_ETHER_EN (0x00000002) +#define FSL_FEC_ECR_ENA_1588 (0x00000010) + +#define LEARNING_AGING_TIMER (10 * HZ) + +/* Define the buffer descriptor structure. */ +typedef struct bufdesc { + unsigned short cbd_datlen; /* Data length */ + unsigned short cbd_sc; /* Control and status info */ + unsigned long cbd_bufaddr; /* Buffer address */ +} cbd_t; + +typedef struct bufdesc_rx { + unsigned short cbd_datlen; /* Data length */ + unsigned short cbd_sc; /* Control and status info */ + unsigned long cbd_bufaddr; /* Buffer address */ +} cbd_t_r; + +/* Forward declarations of some structures to support different PHYs */ +typedef struct _phy_cmd_t { + uint mii_data; + void (*funct)(uint mii_reg, struct net_device *dev); +} phy_cmd_t; + +typedef struct _phy_info_t { + uint id; + char *name; + + const phy_cmd_t *config; + const phy_cmd_t *startup; + const phy_cmd_t *ack_int; + const phy_cmd_t *shutdown; +} phy_info_t; + +struct port_status { + /* 1: link is up, 0: link is down */ + int port1_link_status; + int port2_link_status; + /* 1: blocking, 0: unblocking */ + int port0_block_status; + int port1_block_status; + int port2_block_status; +}; + +/* The switch buffer descriptors track the ring buffers. The rx_bd_base and + * tx_bd_base always point to the base of the buffer descriptors. The + * cur_rx and cur_tx point to the currently available buffer. + * The dirty_tx tracks the current buffer that is being sent by the + * controller. The cur_tx and dirty_tx are equal under both completely + * empty and completely full conditions. The empty/ready indicator in + * the buffer descriptor determines the actual condition. + */ +struct switch_enet_private { + /* Hardware registers of the switch device */ + void __iomem *membase; + void __iomem *macbase; /* MAC address lookup table */ + void __iomem *enetbase; + + struct clk *clk_esw; + struct clk *clk_enet; + struct clk *clk_enet0; + struct clk *clk_enet1; + + struct net_device *netdev; + struct platform_device *pdev; + + int dev_id; + + /* The saved address of a sent-in-place packet/buffer, for skfree(). */ + unsigned char *tx_bounce[TX_RING_SIZE]; + struct sk_buff *tx_skbuff[TX_RING_SIZE]; + struct sk_buff *rx_skbuff[RX_RING_SIZE]; + ushort skb_cur; + ushort skb_dirty; + + /* CPM dual port RAM relative addresses */ + dma_addr_t bd_dma; + + cbd_t *rx_bd_base; /* Address of Rx and Tx buffers. */ + cbd_t *tx_bd_base; + cbd_t *cur_rx, *cur_tx; /* The next free ring entry */ + cbd_t *dirty_tx; /* The ring entries to be free()ed. */ + uint tx_full; + /* hold while accessing the HW like ringbuffer for tx/rx but not MAC */ + spinlock_t hw_lock; + + uint sequence_done; + + int index; + int opened; + int full_duplex; + int msg_enable; + + /* timer related */ + /* current time (for timestamping) */ + int curr_time; + /* flag set by timer when curr_time changed + * and cleared by serving function + */ + int time_changed; + + /* Timer for Aging */ + struct timer_list timer_aging; + int learning_irqhandle_enable; +}; + +struct switch_platform_private { + unsigned long quirks; + int num_slots; /* Slots on controller */ + struct switch_enet_private *fep_host[0]; /* Pointers to hosts */ +}; + +/* Receive is empty */ +#define BD_SC_EMPTY ((unsigned short)0x8000) +/* Transmit is ready */ +#define BD_SC_READY ((unsigned short)0x8000) +/* Last buffer descriptor */ +#define BD_SC_WRAP ((unsigned short)0x2000) +/* Interrupt on change */ +#define BD_SC_INTRPT ((unsigned short)0x1000) +/* Continuous mode */ +#define BD_SC_CM ((unsigned short)0x0200) +/* Rec'd too many idles */ +#define BD_SC_ID ((unsigned short)0x0100) +/* xmt preamble */ +#define BD_SC_P ((unsigned short)0x0100) +/* Break received */ +#define BD_SC_BR ((unsigned short)0x0020) +/* Framing error */ +#define BD_SC_FR ((unsigned short)0x0010) +/* Parity error */ +#define BD_SC_PR ((unsigned short)0x0008) +/* Overrun */ +#define BD_SC_OV ((unsigned short)0x0002) +#define BD_SC_CD ((unsigned short)0x0001) + +/* Buffer descriptor control/status used by Ethernet receive.*/ +#define BD_ENET_RX_EMPTY ((unsigned short)0x8000) +#define BD_ENET_RX_WRAP ((unsigned short)0x2000) +#define BD_ENET_RX_INTR ((unsigned short)0x1000) +#define BD_ENET_RX_LAST ((unsigned short)0x0800) +#define BD_ENET_RX_FIRST ((unsigned short)0x0400) +#define BD_ENET_RX_MISS ((unsigned short)0x0100) +#define BD_ENET_RX_LG ((unsigned short)0x0020) +#define BD_ENET_RX_NO ((unsigned short)0x0010) +#define BD_ENET_RX_SH ((unsigned short)0x0008) +#define BD_ENET_RX_CR ((unsigned short)0x0004) +#define BD_ENET_RX_OV ((unsigned short)0x0002) +#define BD_ENET_RX_CL ((unsigned short)0x0001) + +/* All status bits */ +#define BD_ENET_RX_STATS ((unsigned short)0x013f) + +/* Buffer descriptor control/status used by Ethernet transmit. */ +#define BD_ENET_TX_READY ((unsigned short)0x8000) +#define BD_ENET_TX_PAD ((unsigned short)0x4000) +#define BD_ENET_TX_WRAP ((unsigned short)0x2000) +#define BD_ENET_TX_INTR ((unsigned short)0x1000) +#define BD_ENET_TX_LAST ((unsigned short)0x0800) +#define BD_ENET_TX_TC ((unsigned short)0x0400) +#define BD_ENET_TX_DEF ((unsigned short)0x0200) +#define BD_ENET_TX_HB ((unsigned short)0x0100) +#define BD_ENET_TX_LC ((unsigned short)0x0080) +#define BD_ENET_TX_RL ((unsigned short)0x0040) +#define BD_ENET_TX_RCMASK ((unsigned short)0x003c) +#define BD_ENET_TX_UN ((unsigned short)0x0002) +#define BD_ENET_TX_CSL ((unsigned short)0x0001) + +/* All status bits */ +#define BD_ENET_TX_STATS ((unsigned short)0x03ff) + +/* Copy from validation code */ +#define RX_BUFFER_SIZE 1520 +#define TX_BUFFER_SIZE 1520 +#define NUM_RXBDS 20 +#define NUM_TXBDS 20 + +#define TX_BD_R 0x8000 +#define TX_BD_TO1 0x4000 +#define TX_BD_W 0x2000 +#define TX_BD_TO2 0x1000 +#define TX_BD_L 0x0800 +#define TX_BD_TC 0x0400 + +#define TX_BD_INT 0x40000000 +#define TX_BD_TS 0x20000000 +#define TX_BD_PINS 0x10000000 +#define TX_BD_IINS 0x08000000 +#define TX_BD_TXE 0x00008000 +#define TX_BD_UE 0x00002000 +#define TX_BD_EE 0x00001000 +#define TX_BD_FE 0x00000800 +#define TX_BD_LCE 0x00000400 +#define TX_BD_OE 0x00000200 +#define TX_BD_TSE 0x00000100 +#define TX_BD_BDU 0x80000000 + +#define RX_BD_E 0x8000 +#define RX_BD_R01 0x4000 +#define RX_BD_W 0x2000 +#define RX_BD_R02 0x1000 +#define RX_BD_L 0x0800 +#define RX_BD_M 0x0100 +#define RX_BD_BC 0x0080 +#define RX_BD_MC 0x0040 +#define RX_BD_LG 0x0020 +#define RX_BD_NO 0x0010 +#define RX_BD_CR 0x0004 +#define RX_BD_OV 0x0002 +#define RX_BD_TR 0x0001 + +#define RX_BD_ME 0x80000000 +#define RX_BD_PE 0x04000000 +#define RX_BD_CE 0x02000000 +#define RX_BD_UC 0x01000000 +#define RX_BD_INT 0x00800000 +#define RX_BD_ICE 0x00000020 +#define RX_BD_PCR 0x00000010 +#define RX_BD_VLAN 0x00000004 +#define RX_BD_IPV6 0x00000002 +#define RX_BD_FRAG 0x00000001 +#define RX_BD_BDU 0x80000000 + +/* Address Table size in bytes(2048 64bit entry ) */ +#define ESW_ATABLE_MEM_SIZE (2048 * 8) +/* How many 64-bit elements fit in the address table */ +#define ESW_ATABLE_MEM_NUM_ENTRIES (2048) +/* Address Table Maximum number of entries in each Slot */ +#define ATABLE_ENTRY_PER_SLOT 8 +/* log2(ATABLE_ENTRY_PER_SLOT) */ +#define ATABLE_ENTRY_PER_SLOT_bits 3 +/* entry size in byte */ +#define ATABLE_ENTRY_SIZE 8 +/* slot size in byte */ +#define ATABLE_SLOT_SIZE (ATABLE_ENTRY_PER_SLOT * ATABLE_ENTRY_SIZE) +/* width of timestamp variable (bits) within address table entry */ +#define AT_DENTRY_TIMESTAMP_WIDTH 10 +/* number of bits for port number storage */ +#define AT_DENTRY_PORT_WIDTH 4 +/* number of bits for port bitmask number storage */ +#define AT_SENTRY_PORT_WIDTH 7 +/* address table static entry port bitmask start address bit */ +#define AT_SENTRY_PORTMASK_shift 21 +/* number of bits for port priority storage */ +#define AT_SENTRY_PRIO_WIDTH 7 +/* address table static entry priority start address bit */ +#define AT_SENTRY_PRIO_shift 18 +/* address table dynamic entry port start address bit */ +#define AT_DENTRY_PORT_shift 28 +/* address table dynamic entry timestamp start address bit */ +#define AT_DENTRY_TIME_shift 18 +/* address table entry record type start address bit */ +#define AT_ENTRY_TYPE_shift 17 +/* address table entry record type bit: 1 static, 0 dynamic */ +#define AT_ENTRY_TYPE_STATIC 1 +#define AT_ENTRY_TYPE_DYNAMIC 0 +/* address table entry record valid start address bit */ +#define AT_ENTRY_VALID_shift 16 +#define AT_ENTRY_RECORD_VALID 1 + +#define AT_EXTRACT_VALID(x) \ + ((x >> AT_ENTRY_VALID_shift) & AT_ENTRY_RECORD_VALID) + +#define AT_EXTRACT_PORTMASK(x) \ + ((x >> AT_SENTRY_PORTMASK_shift) & AT_SENTRY_PORT_WIDTH) + +#define AT_EXTRACT_PRIO(x) \ + ((x >> AT_SENTRY_PRIO_shift) & AT_SENTRY_PRIO_WIDTH) + +/* return block corresponding to the 8 bit hash value calculated */ +#define GET_BLOCK_PTR(hash) (hash << 3) +#define AT_EXTRACT_TIMESTAMP(x) \ + ((x >> AT_DENTRY_TIME_shift) & ((1 << AT_DENTRY_TIMESTAMP_WIDTH) - 1)) +#define AT_EXTRACT_PORT(x) \ + ((x >> AT_DENTRY_PORT_shift) & ((1 << AT_DENTRY_PORT_WIDTH) - 1)) +#define AT_SEXTRACT_PORT(x) \ + ((~((x >> AT_SENTRY_PORTMASK_shift) & \ + ((1 << AT_DENTRY_PORT_WIDTH) - 1))) >> 1) +#define TIMEDELTA(newtime, oldtime) \ + ((newtime - oldtime) & \ + ((1 << AT_DENTRY_TIMESTAMP_WIDTH) - 1)) + +#define AT_EXTRACT_IP_PROTOCOL(x) ((x >> 8) & 0xff) +#define AT_EXTRACT_TCP_UDP_PORT(x) ((x >> 16) & 0xffff) + +/* increment time value respecting modulo. */ +#define TIMEINCREMENT(time) \ + ((time) = ((time)+1) & ((1 << AT_DENTRY_TIMESTAMP_WIDTH)-1)) + +/* Bit definitions and macros for FSL_ESW_REVISION */ +#define FSL_ESW_REVISION_CORE_REVISION(x) (((x)&0x0000FFFF) << 0) +#define FSL_ESW_REVISION_CUSTOMER_REVISION(x) (((x)&0x0000FFFF) << 16) + +/* Bit definitions and macros for FSL_ESW_PER */ +#define FSL_ESW_PER_TE0 (0x00000001) +#define FSL_ESW_PER_TE1 (0x00000002) +#define FSL_ESW_PER_TE2 (0x00000004) +#define FSL_ESW_PER_RE0 (0x00010000) +#define FSL_ESW_PER_RE1 (0x00020000) +#define FSL_ESW_PER_RE2 (0x00040000) + +/* Bit definitions and macros for FSL_ESW_VLANV */ +#define FSL_ESW_VLANV_VV0 (0x00000001) +#define FSL_ESW_VLANV_VV1 (0x00000002) +#define FSL_ESW_VLANV_VV2 (0x00000004) +#define FSL_ESW_VLANV_DU0 (0x00010000) +#define FSL_ESW_VLANV_DU1 (0x00020000) +#define FSL_ESW_VLANV_DU2 (0x00040000) + +/* Bit definitions and macros for FSL_ESW_DBCR */ +#define FSL_ESW_DBCR_P0 (0x00000001) +#define FSL_ESW_DBCR_P1 (0x00000002) +#define FSL_ESW_DBCR_P2 (0x00000004) + +/* Bit definitions and macros for FSL_ESW_DMCR */ +#define FSL_ESW_DMCR_P0 (0x00000001) +#define FSL_ESW_DMCR_P1 (0x00000002) +#define FSL_ESW_DMCR_P2 (0x00000004) + +/* Bit definitions and macros for FSL_ESW_BKLR */ +#define FSL_ESW_BKLR_BE0 (0x00000001) +#define FSL_ESW_BKLR_BE1 (0x00000002) +#define FSL_ESW_BKLR_BE2 (0x00000004) +#define FSL_ESW_BKLR_LD0 (0x00010000) +#define FSL_ESW_BKLR_LD1 (0x00020000) +#define FSL_ESW_BKLR_LD2 (0x00040000) +#define FSL_ESW_BKLR_LDX (0x00070007) + +/* Bit definitions and macros for FSL_ESW_BMPC */ +#define FSL_ESW_BMPC_PORT(x) (((x) & 0x0000000F) << 0) +#define FSL_ESW_BMPC_MSG_TX (0x00000020) +#define FSL_ESW_BMPC_EN (0x00000040) +#define FSL_ESW_BMPC_DIS (0x00000080) +#define FSL_ESW_BMPC_PRIORITY(x) (((x) & 0x00000007) << 13) +#define FSL_ESW_BMPC_PORTMASK(x) (((x) & 0x00000007) << 16) + +/* Bit definitions and macros for FSL_ESW_MODE */ +#define FSL_ESW_MODE_SW_RST (0x00000001) +#define FSL_ESW_MODE_SW_EN (0x00000002) +#define FSL_ESW_MODE_STOP (0x00000080) +#define FSL_ESW_MODE_CRC_TRAN (0x00000100) +#define FSL_ESW_MODE_P0CT (0x00000200) +#define FSL_ESW_MODE_STATRST (0x80000000) + +/* Bit definitions and macros for FSL_ESW_VIMSEL */ +#define FSL_ESW_VIMSEL_IM0(x) (((x) & 0x00000003) << 0) +#define FSL_ESW_VIMSEL_IM1(x) (((x) & 0x00000003) << 2) +#define FSL_ESW_VIMSEL_IM2(x) (((x) & 0x00000003) << 4) + +/* Bit definitions and macros for FSL_ESW_VOMSEL */ +#define FSL_ESW_VOMSEL_OM0(x) (((x) & 0x00000003) << 0) +#define FSL_ESW_VOMSEL_OM1(x) (((x) & 0x00000003) << 2) +#define FSL_ESW_VOMSEL_OM2(x) (((x) & 0x00000003) << 4) + +/* Bit definitions and macros for FSL_ESW_VIMEN */ +#define FSL_ESW_VIMEN_EN0 (0x00000001) +#define FSL_ESW_VIMEN_EN1 (0x00000002) +#define FSL_ESW_VIMEN_EN2 (0x00000004) + +/* Bit definitions and macros for FSL_ESW_VID */ +#define FSL_ESW_VID_TAG(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_MCR */ +#define FSL_ESW_MCR_PORT(x) (((x) & 0x0000000F) << 0) +#define FSL_ESW_MCR_MEN (0x00000010) +#define FSL_ESW_MCR_INGMAP (0x00000020) +#define FSL_ESW_MCR_EGMAP (0x00000040) +#define FSL_ESW_MCR_INGSA (0x00000080) +#define FSL_ESW_MCR_INGDA (0x00000100) +#define FSL_ESW_MCR_EGSA (0x00000200) +#define FSL_ESW_MCR_EGDA (0x00000400) + +/* Bit definitions and macros for FSL_ESW_EGMAP */ +#define FSL_ESW_EGMAP_EG0 (0x00000001) +#define FSL_ESW_EGMAP_EG1 (0x00000002) +#define FSL_ESW_EGMAP_EG2 (0x00000004) + +/* Bit definitions and macros for FSL_ESW_INGMAP */ +#define FSL_ESW_INGMAP_ING0 (0x00000001) +#define FSL_ESW_INGMAP_ING1 (0x00000002) +#define FSL_ESW_INGMAP_ING2 (0x00000004) + +/* Bit definitions and macros for FSL_ESW_INGSAL */ +#define FSL_ESW_INGSAL_ADDLOW(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_INGSAH */ +#define FSL_ESW_INGSAH_ADDHIGH(x) (((x) & 0x0000FFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_INGDAL */ +#define FSL_ESW_INGDAL_ADDLOW(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_INGDAH */ +#define FSL_ESW_INGDAH_ADDHIGH(x) (((x) & 0x0000FFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_ENGSAL */ +#define FSL_ESW_ENGSAL_ADDLOW(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_ENGSAH */ +#define FSL_ESW_ENGSAH_ADDHIGH(x) (((x) & 0x0000FFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_ENGDAL */ +#define FSL_ESW_ENGDAL_ADDLOW(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_ENGDAH */ +#define FSL_ESW_ENGDAH_ADDHIGH(x) (((x) & 0x0000FFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_MCVAL */ +#define FSL_ESW_MCVAL_COUNT(x) (((x) & 0x000000FF) << 0) + +/* Bit definitions and macros for FSL_ESW_MMSR */ +#define FSL_ESW_MMSR_BUSY (0x00000001) +#define FSL_ESW_MMSR_NOCELL (0x00000002) +#define FSL_ESW_MMSR_MEMFULL (0x00000004) +#define FSL_ESW_MMSR_MFLATCH (0x00000008) +#define FSL_ESW_MMSR_DQ_GRNT (0x00000040) +#define FSL_ESW_MMSR_CELLS_AVAIL(x) (((x) & 0x000000FF) << 16) + +/* Bit definitions and macros for FSL_ESW_LMT */ +#define FSL_ESW_LMT_THRESH(x) (((x) & 0x000000FF) << 0) + +/* Bit definitions and macros for FSL_ESW_LFC */ +#define FSL_ESW_LFC_COUNT(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_PCSR */ +#define FSL_ESW_PCSR_PC0 (0x00000001) +#define FSL_ESW_PCSR_PC1 (0x00000002) +#define FSL_ESW_PCSR_PC2 (0x00000004) + +/* Bit definitions and macros for FSL_ESW_IOSR */ +#define FSL_ESW_IOSR_OR0 (0x00000001) +#define FSL_ESW_IOSR_OR1 (0x00000002) +#define FSL_ESW_IOSR_OR2 (0x00000004) + +/* Bit definitions and macros for FSL_ESW_QWT */ +#define FSL_ESW_QWT_Q0WT(x) (((x) & 0x0000001F) << 0) +#define FSL_ESW_QWT_Q1WT(x) (((x) & 0x0000001F) << 8) +#define FSL_ESW_QWT_Q2WT(x) (((x) & 0x0000001F) << 16) +#define FSL_ESW_QWT_Q3WT(x) (((x) & 0x0000001F) << 24) + +/* Bit definitions and macros for FSL_ESW_P0BCT */ +#define FSL_ESW_P0BCT_THRESH(x) (((x) & 0x000000FF) << 0) + +/* Bit definitions and macros for FSL_ESW_P0FFEN */ +#define FSL_ESW_P0FFEN_FEN (0x00000001) +#define FSL_ESW_P0FFEN_FD(x) (((x) & 0x00000003) << 2) + +/* Bit definitions and macros for FSL_ESW_PSNP */ +#define FSL_ESW_PSNP_EN (0x00000001) +#define FSL_ESW_PSNP_MODE(x) (((x) & 0x00000003) << 1) +#define FSL_ESW_PSNP_CD (0x00000008) +#define FSL_ESW_PSNP_CS (0x00000010) +#define FSL_ESW_PSNP_PORT_COMPARE(x) (((x) & 0x0000FFFF) << 16) + +/* Bit definitions and macros for FSL_ESW_IPSNP */ +#define FSL_ESW_IPSNP_EN (0x00000001) +#define FSL_ESW_IPSNP_MODE(x) (((x) & 0x00000003) << 1) +#define FSL_ESW_IPSNP_PROTOCOL(x) (((x) & 0x000000FF) << 8) + +/* Bit definitions and macros for FSL_ESW_PVRES */ +#define FSL_ESW_PVRES_PRI0(x) (((x) & 0x00000007) << 0) +#define FSL_ESW_PVRES_PRI1(x) (((x) & 0x00000007) << 3) +#define FSL_ESW_PVRES_PRI2(x) (((x) & 0x00000007) << 6) +#define FSL_ESW_PVRES_PRI3(x) (((x) & 0x00000007) << 9) +#define FSL_ESW_PVRES_PRI4(x) (((x) & 0x00000007) << 12) +#define FSL_ESW_PVRES_PRI5(x) (((x) & 0x00000007) << 15) +#define FSL_ESW_PVRES_PRI6(x) (((x) & 0x00000007) << 18) +#define FSL_ESW_PVRES_PRI7(x) (((x) & 0x00000007) << 21) + +/* Bit definitions and macros for FSL_ESW_IPRES */ +#define FSL_ESW_IPRES_ADDRESS(x) (((x) & 0x000000FF) << 0) +#define FSL_ESW_IPRES_IPV4SEL (0x00000100) +#define FSL_ESW_IPRES_PRI0(x) (((x) & 0x00000003) << 9) +#define FSL_ESW_IPRES_PRI1(x) (((x) & 0x00000003) << 11) +#define FSL_ESW_IPRES_PRI2(x) (((x) & 0x00000003) << 13) +#define FSL_ESW_IPRES_READ (0x80000000) + +/* Bit definitions and macros for FSL_ESW_PRES */ +#define FSL_ESW_PRES_VLAN (0x00000001) +#define FSL_ESW_PRES_IP (0x00000002) +#define FSL_ESW_PRES_MAC (0x00000004) +#define FSL_ESW_PRES_DFLT_PRI(x) (((x) & 0x00000007) << 4) + +/* Bit definitions and macros for FSL_ESW_PID */ +#define FSL_ESW_PID_VLANID(x) (((x) & 0x0000FFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_VRES */ +#define FSL_ESW_VRES_P0 (0x00000001) +#define FSL_ESW_VRES_P1 (0x00000002) +#define FSL_ESW_VRES_P2 (0x00000004) +#define FSL_ESW_VRES_VLANID(x) (((x) & 0x00000FFF) << 3) + +/* Bit definitions and macros for FSL_ESW_DISCN */ +#define FSL_ESW_DISCN_COUNT(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_DISCB */ +#define FSL_ESW_DISCB_COUNT(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_NDISCN */ +#define FSL_ESW_NDISCN_COUNT(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_NDISCB */ +#define FSL_ESW_NDISCB_COUNT(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_POQC */ +#define FSL_ESW_POQC_COUNT(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_PMVID */ +#define FSL_ESW_PMVID_COUNT(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_PMVTAG */ +#define FSL_ESW_PMVTAG_COUNT(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_PBL */ +#define FSL_ESW_PBL_COUNT(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_ISR */ +#define FSL_ESW_ISR_EBERR (0x00000001) +#define FSL_ESW_ISR_RXB (0x00000002) +#define FSL_ESW_ISR_RXF (0x00000004) +#define FSL_ESW_ISR_TXB (0x00000008) +#define FSL_ESW_ISR_TXF (0x00000010) +#define FSL_ESW_ISR_QM (0x00000020) +#define FSL_ESW_ISR_OD0 (0x00000040) +#define FSL_ESW_ISR_OD1 (0x00000080) +#define FSL_ESW_ISR_OD2 (0x00000100) +#define FSL_ESW_ISR_LRN (0x00000200) + +/* Bit definitions and macros for FSL_ESW_IMR */ +#define FSL_ESW_IMR_EBERR (0x00000001) +#define FSL_ESW_IMR_RXB (0x00000002) +#define FSL_ESW_IMR_RXF (0x00000004) +#define FSL_ESW_IMR_TXB (0x00000008) +#define FSL_ESW_IMR_TXF (0x00000010) +#define FSL_ESW_IMR_QM (0x00000020) +#define FSL_ESW_IMR_OD0 (0x00000040) +#define FSL_ESW_IMR_OD1 (0x00000080) +#define FSL_ESW_IMR_OD2 (0x00000100) +#define FSL_ESW_IMR_LRN (0x00000200) + +/* Bit definitions and macros for FSL_ESW_RDSR */ +#define FSL_ESW_RDSR_ADDRESS(x) (((x) & 0x3FFFFFFF) << 2) + +/* Bit definitions and macros for FSL_ESW_TDSR */ +#define FSL_ESW_TDSR_ADDRESS(x) (((x) & 0x3FFFFFFF) << 2) + +/* Bit definitions and macros for FSL_ESW_MRBR */ +#define FSL_ESW_MRBR_SIZE(x) (((x) & 0x000003FF) << 4) + +/* Bit definitions and macros for FSL_ESW_RDAR */ +#define FSL_ESW_RDAR_R_DES_ACTIVE (0x01000000) + +/* Bit definitions and macros for FSL_ESW_TDAR */ +#define FSL_ESW_TDAR_X_DES_ACTIVE (0x01000000) + +/* Bit definitions and macros for FSL_ESW_LREC0 */ +#define FSL_ESW_LREC0_MACADDR0(x) (((x) & 0xFFFFFFFF) << 0) + +/* Bit definitions and macros for FSL_ESW_LREC1 */ +#define FSL_ESW_LREC1_MACADDR1(x) (((x) & 0x0000FFFF) << 0) +#define FSL_ESW_LREC1_HASH(x) (((x) & 0x000000FF) << 16) +#define FSL_ESW_LREC1_SWPORT(x) (((x) & 0x00000003) << 24) + +/* Bit definitions and macros for FSL_ESW_LSR */ +#define FSL_ESW_LSR_DA (0x00000001) + +/* port mirroring port number match */ +#define MIRROR_EGRESS_PORT_MATCH 1 +#define MIRROR_INGRESS_PORT_MATCH 2 + +/* port mirroring mac address match */ +#define MIRROR_EGRESS_SOURCE_MATCH 1 +#define MIRROR_INGRESS_SOURCE_MATCH 2 +#define MIRROR_EGRESS_DESTINATION_MATCH 3 +#define MIRROR_INGRESS_DESTINATION_MATCH 4 + +#endif /* FSL_L2_SWITCH_H */ diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index ba84fc3637b1..ffae517e5e7a 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -708,8 +708,6 @@ static struct phy_driver ksphy_driver[] = { .read_status = genphy_read_status, .ack_interrupt = kszphy_ack_interrupt, .config_intr = kszphy_config_intr, - .suspend = genphy_suspend, - .resume = genphy_resume, .driver = { .owner = THIS_MODULE,}, }, { .phy_id = PHY_ID_KSZ8041RNLI, diff --git a/drivers/pinctrl/freescale/pinctrl-imx.c b/drivers/pinctrl/freescale/pinctrl-imx.c index 398ec45aadef..3035d33f5957 100644 --- a/drivers/pinctrl/freescale/pinctrl-imx.c +++ b/drivers/pinctrl/freescale/pinctrl-imx.c @@ -24,6 +24,7 @@ #include <linux/pinctrl/pinctrl.h> #include <linux/pinctrl/pinmux.h> #include <linux/slab.h> +#include <linux/syscore_ops.h> #include "../core.h" #include "pinctrl-imx.h" @@ -42,6 +43,8 @@ struct imx_pinctrl { void __iomem *base; void __iomem *input_sel_base; const struct imx_pinctrl_soc_info *info; + u32 *mux_regs; + u32 *input_regs; }; static const inline struct imx_pin_group *imx_pinctrl_find_group_by_name( @@ -341,6 +344,36 @@ mux_pin: return 0; } +static void imx_pmx_gpio_disable_free(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, unsigned offset) +{ + struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev); + const struct imx_pinctrl_soc_info *info = ipctl->info; + const struct imx_pin_reg *pin_reg; + u32 reg; + + /* + * Only Vybrid has the input/output buffer enable flags (IBE/OBE) + * They are part of the shared mux/conf register. + */ + if (!(info->flags & SHARE_MUX_CONF_REG)) + return; + + pin_reg = &info->pin_regs[offset]; + if (pin_reg->mux_reg == -1) + return; + + reg = readl(ipctl->base + pin_reg->mux_reg); + + /* Only change pad configuration if pad is still a GPIO */ + if (reg & (0x7 << 20)) + return; + + /* Clear IBE/OBE/PUE to disable the pin (Hi-Z) */ + reg &= ~0x7; + writel(reg, ipctl->base + pin_reg->mux_reg); +} + static int imx_pmx_gpio_set_direction(struct pinctrl_dev *pctldev, struct pinctrl_gpio_range *range, unsigned offset, bool input) { @@ -377,6 +410,7 @@ static const struct pinmux_ops imx_pmx_ops = { .get_function_groups = imx_pmx_get_groups, .set_mux = imx_pmx_set, .gpio_request_enable = imx_pmx_gpio_request_enable, + .gpio_disable_free = imx_pmx_gpio_disable_free, .gpio_set_direction = imx_pmx_gpio_set_direction, }; @@ -689,6 +723,53 @@ static int imx_pinctrl_probe_dt(struct platform_device *pdev, return 0; } +static int __maybe_unused imx_pinctrl_suspend(struct device *dev) +{ + struct imx_pinctrl *ipctl = dev_get_drvdata(dev); + const struct imx_pinctrl_soc_info *info = ipctl->info; + int i; + + for (i = 0; i < info->npins; i++) { + const struct imx_pin_reg *pin_reg = &info->pin_regs[i]; + if (pin_reg->mux_reg == -1) + continue; + + ipctl->mux_regs[i] = readl(ipctl->base + pin_reg->mux_reg); + } + + for (i = 0; i < info->ninput_regs; i++) + ipctl->input_regs[i] = readl(ipctl->base + + info->input_regs_offset + i * sizeof(u32 *)); + + return 0; +} + +static int __maybe_unused imx_pinctrl_resume(struct device *dev) +{ + struct imx_pinctrl *ipctl = dev_get_drvdata(dev); + const struct imx_pinctrl_soc_info *info = ipctl->info; + const struct imx_pin_reg *pin_reg; + int i; + + for (i = 0; i < info->npins; i++) { + pin_reg = &info->pin_regs[i]; + if (pin_reg->mux_reg == -1) + continue; + + writel(ipctl->mux_regs[i], ipctl->base + pin_reg->mux_reg); + } + + for (i = 0; i < info->ninput_regs; i++) + writel(ipctl->input_regs[i], ipctl->base + + info->input_regs_offset + i * sizeof(u32 *)); + + return 0; +} + +const struct dev_pm_ops imx_pinctrl_dev_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(imx_pinctrl_suspend, imx_pinctrl_resume) +}; + int imx_pinctrl_probe(struct platform_device *pdev, struct imx_pinctrl_soc_info *info) { @@ -719,6 +800,18 @@ int imx_pinctrl_probe(struct platform_device *pdev, info->pin_regs[i].conf_reg = -1; } +#ifdef CONFIG_PM_SLEEP + ipctl->mux_regs = devm_kzalloc(&pdev->dev, sizeof(u32 *) * + info->npins, GFP_KERNEL); + if (!ipctl->mux_regs) + return -ENOMEM; + + ipctl->input_regs = devm_kzalloc(&pdev->dev, sizeof(u32 *) * + info->ninput_regs, GFP_KERNEL); + if (!ipctl->input_regs) + return -ENOMEM; +#endif + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ipctl->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(ipctl->base)) diff --git a/drivers/pinctrl/freescale/pinctrl-imx.h b/drivers/pinctrl/freescale/pinctrl-imx.h index 2a592f657c18..56851a66c27b 100644 --- a/drivers/pinctrl/freescale/pinctrl-imx.h +++ b/drivers/pinctrl/freescale/pinctrl-imx.h @@ -81,6 +81,8 @@ struct imx_pinctrl_soc_info { unsigned int group_index; struct imx_pmx_func *functions; unsigned int nfunctions; + unsigned int input_regs_offset; + unsigned int ninput_regs; unsigned int flags; }; @@ -99,4 +101,5 @@ struct imx_pinctrl_soc_info { int imx_pinctrl_probe(struct platform_device *pdev, struct imx_pinctrl_soc_info *info); int imx_pinctrl_remove(struct platform_device *pdev); +extern const struct dev_pm_ops imx_pinctrl_dev_pm_ops; #endif /* __DRIVERS_PINCTRL_IMX_H */ diff --git a/drivers/pinctrl/freescale/pinctrl-vf610.c b/drivers/pinctrl/freescale/pinctrl-vf610.c index 587d1ff6210e..b6280a8ef958 100644 --- a/drivers/pinctrl/freescale/pinctrl-vf610.c +++ b/drivers/pinctrl/freescale/pinctrl-vf610.c @@ -19,6 +19,9 @@ #include "pinctrl-imx.h" +#define VF610_INPUT_REG_CNT 49 +#define VF610_INPUT_REG_BASE 0x2ec + enum vf610_pads { VF610_PAD_PTA6 = 0, VF610_PAD_PTA8 = 1, @@ -299,6 +302,8 @@ static const struct pinctrl_pin_desc vf610_pinctrl_pads[] = { static struct imx_pinctrl_soc_info vf610_pinctrl_info = { .pins = vf610_pinctrl_pads, .npins = ARRAY_SIZE(vf610_pinctrl_pads), + .input_regs_offset = VF610_INPUT_REG_BASE, + .ninput_regs = VF610_INPUT_REG_CNT, .flags = SHARE_MUX_CONF_REG | ZERO_OFFSET_VALID, }; @@ -316,6 +321,7 @@ static struct platform_driver vf610_pinctrl_driver = { .driver = { .name = "vf610-pinctrl", .of_match_table = vf610_pinctrl_of_match, + .pm = &imx_pinctrl_dev_pm_ops, }, .probe = vf610_pinctrl_probe, .remove = imx_pinctrl_remove, diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index 28c711f0ac6b..1dd53b1ebf6b 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -41,6 +41,15 @@ config STE_MODEM_RPROC This can be either built-in or a loadable module. If unsure say N. +config VF610_CM4_RPROC + tristate "Freescale Vybrid Cortex-M4 remoteproc support" + depends on SOC_VF610 + select REMOTEPROC + select RPMSG + help + Say y here to support Freescale Vybrid's secondary core via the + remote processor framework. + config WKUP_M3_RPROC tristate "AMx3xx Wakeup M3 remoteproc support" depends on SOC_AM33XX || SOC_AM43XX diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index 81b04d1e2e58..df1814ff283c 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile @@ -9,5 +9,6 @@ remoteproc-y += remoteproc_virtio.o remoteproc-y += remoteproc_elf_loader.o obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o obj-$(CONFIG_STE_MODEM_RPROC) += ste_modem_rproc.o +obj-$(CONFIG_VF610_CM4_RPROC) += vf610_cm4_rproc.o obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index 4f7ce0097191..44d0f5995f54 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -799,12 +799,11 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) struct resource_table *table, *loaded_table; int ret, tablesz; - if (!rproc->table_ptr) - return -ENOMEM; - ret = rproc_fw_sanity_check(rproc, fw); - if (ret) + if (ret) { + dev_err(dev, "rproc_fw_sanity_check returned %d\n", ret); return ret; + } dev_info(dev, "Booting fw image %s, size %zd\n", name, fw->size); @@ -823,20 +822,22 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) /* look for the resource table */ table = rproc_find_rsc_table(rproc, fw, &tablesz); - if (!table) - goto clean_up; - - /* Verify that resource table in loaded fw is unchanged */ - if (rproc->table_csum != crc32(0, table, tablesz)) { - dev_err(dev, "resource checksum failed, fw changed?\n"); - goto clean_up; - } + if (!table) { + dev_info(dev, "No resource table found, continuing...\n"); + } else { + /* Verify that resource table in loaded fw is unchanged */ + if (rproc->table_csum != crc32(0, table, tablesz)) { + dev_err(dev, "resource checksum failed, fw changed?\n"); + goto clean_up; + } - /* handle fw resources which are required to boot rproc */ - ret = rproc_handle_resources(rproc, tablesz, rproc_loading_handlers); - if (ret) { - dev_err(dev, "Failed to process resources: %d\n", ret); - goto clean_up; + /* handle fw resources which are required to boot rproc */ + ret = rproc_handle_resources(rproc, tablesz, + rproc_loading_handlers); + if (ret) { + dev_err(dev, "Failed to process resources: %d\n", ret); + goto clean_up; + } } /* load the ELF segments to memory */ @@ -853,14 +854,15 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) * In order to pass this information to the remote device we must * copy this information to device memory. */ - loaded_table = rproc_find_loaded_rsc_table(rproc, fw); - if (!loaded_table) { - ret = -EINVAL; - goto clean_up; + if (table) { + loaded_table = rproc_find_loaded_rsc_table(rproc, fw); + if (!loaded_table) { + ret = -EINVAL; + goto clean_up; + } + memcpy(loaded_table, rproc->cached_table, tablesz); } - memcpy(loaded_table, rproc->cached_table, tablesz); - /* power up the remote processor */ ret = rproc->ops->start(rproc); if (ret) { diff --git a/drivers/remoteproc/vf610_cm4_rproc.c b/drivers/remoteproc/vf610_cm4_rproc.c new file mode 100644 index 000000000000..da45162abc12 --- /dev/null +++ b/drivers/remoteproc/vf610_cm4_rproc.c @@ -0,0 +1,259 @@ +/* + * Freescale VF610 Cortex-M4 Remote Processor driver + * + * Copyright (C) 2016 Toradex AG + * + * Based on wkup_m3_rproc.c by: + * Copyright (C) 2014-2015 Texas Instruments, Inc. + * + * This program 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 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. + */ + +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/remoteproc.h> + +#include "remoteproc_internal.h" + +#define CM4_MEM_MAX 2 + +/* + * struct vf610_m4_mem - Cm4 internal memory structure + * @cpu_addr: MPU virtual address of the memory region + * @bus_addr: Bus address used to access the memory region + * @dev_addr: Device address from Wakeup M4 view + * @size: Size of the memory region + */ +struct vf610_m4_mem { + void __iomem *cpu_addr; + phys_addr_t bus_addr; + u32 dev_addr; + size_t size; +}; + +/* + * struct vf610_m4_rproc - Cm4 remote processor state + * @rproc: rproc handle + * @pdev: pointer to platform device + * @mem: Cm4 memory information + */ +struct vf610_m4_rproc { + struct rproc *rproc; + struct platform_device *pdev; + struct vf610_m4_mem mem[CM4_MEM_MAX]; + struct regmap *ccm; + struct regmap *src; +}; + +static int vf610_m4_rproc_start(struct rproc *rproc) +{ + struct vf610_m4_rproc *cm4 = rproc->priv; + + regmap_write(cm4->src, 0x28, rproc->bootaddr); + regmap_write(cm4->ccm, 0x8c, 0x00015a5a); + + return 0; +} + +static int vf610_m4_rproc_stop(struct rproc *rproc) +{ + return -EINVAL; +} + +static void *vf610_m4_rproc_da_to_va(struct rproc *rproc, u64 da, int len) +{ + struct vf610_m4_rproc *cm4 = rproc->priv; + void *va = NULL; + u32 offset; + int i; + + if (len <= 0) + return NULL; + + for (i = 0; i < CM4_MEM_MAX; i++) { + if (da >= cm4->mem[i].dev_addr && da + len <= + cm4->mem[i].dev_addr + cm4->mem[i].size) { + offset = da - cm4->mem[i].dev_addr; + /* __force to make sparse happy with type conversion */ + va = (__force void *)(cm4->mem[i].cpu_addr + offset); + break; + } + } + + return va; +} + +static void vf610_m4_rproc_kick(struct rproc *rproc, int vqid) +{ + /* + * We provide this just to prevent Linux from complaining + * and causing a kernel panic. + */ +} + +static struct rproc_ops vf610_m4_rproc_ops = { + .start = vf610_m4_rproc_start, + .stop = vf610_m4_rproc_stop, + .da_to_va = vf610_m4_rproc_da_to_va, + .kick = vf610_m4_rproc_kick, +}; + +static const struct of_device_id vf610_m4_rproc_of_match[] = { + { .compatible = "fsl,vf610-m4", }, + {}, +}; + +static int vf610_m4_rproc_probe(struct platform_device *pdev) +{ + const char *mem_names[CM4_MEM_MAX] = { "pc_ocram", "ps_ocram" }; + struct device *dev = &pdev->dev; + struct vf610_m4_rproc *cm4; + const char *fw_name; + struct rproc *rproc; + struct resource *res; + const __be32 *addrp; + u32 l4_offset = 0; + u64 size; + int ret; + int i, j; + + ret = of_property_read_string(dev->of_node, "fsl,firmware", + &fw_name); + if (ret) { + dev_err(dev, "No firmware filename given\n"); + return -ENODEV; + } + + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "pm_runtime_get_sync() failed\n"); + goto err; + } + + rproc = rproc_alloc(dev, "vf610_m4", &vf610_m4_rproc_ops, + fw_name, sizeof(*cm4)); + if (!rproc) { + ret = -ENOMEM; + goto err; + } + + cm4 = rproc->priv; + cm4->rproc = rproc; + cm4->pdev = pdev; + + cm4->src = syscon_regmap_lookup_by_compatible("fsl,vf610-src"); + cm4->ccm = syscon_regmap_lookup_by_compatible("fsl,vf610-ccm"); + + for (i = 0; i < ARRAY_SIZE(mem_names); i++) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + mem_names[i]); + + /* + * The Cortex-M4 has address aliases, which means we + * have two device addresses which map to the same + * bus address. From Linux side, just use the already + * mapped region for those cases. + */ + for (j = i - 1; j >= 0; j--) { + if (res->start == cm4->mem[j].bus_addr) { + cm4->mem[i].cpu_addr = + cm4->mem[j].cpu_addr; + break; + } + } + + /* No previous mapping found, create a new mapping */ + if (j < 0) { + cm4->mem[i].cpu_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(cm4->mem[i].cpu_addr)) { + dev_err(&pdev->dev, + "devm_ioremap_resource failed for resource %d\n", i); + ret = PTR_ERR(cm4->mem[i].cpu_addr); + goto err; + } + } + + cm4->mem[i].bus_addr = res->start; + cm4->mem[i].size = resource_size(res); + addrp = of_get_address(dev->of_node, i, &size, NULL); + cm4->mem[i].dev_addr = be32_to_cpu(*addrp) - l4_offset; + } + + dev_set_drvdata(dev, rproc); + + ret = rproc_add(rproc); + if (ret) { + dev_err(dev, "rproc_add failed\n"); + goto err_put_rproc; + } + + rproc_boot(rproc); + + return 0; + +err_put_rproc: + rproc_put(rproc); +err: + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + return ret; +} + +static int vf610_m4_rproc_remove(struct platform_device *pdev) +{ + struct rproc *rproc = platform_get_drvdata(pdev); + + rproc_del(rproc); + rproc_put(rproc); + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int vf610_m4_rpm_suspend(struct device *dev) +{ + return -EBUSY; +} + +static int vf610_m4_rpm_resume(struct device *dev) +{ + return 0; +} +#endif + +static const struct dev_pm_ops vf610_m4_rproc_pm_ops = { + SET_RUNTIME_PM_OPS(vf610_m4_rpm_suspend, vf610_m4_rpm_resume, NULL) +}; + +static struct platform_driver vf610_m4_rproc_driver = { + .probe = vf610_m4_rproc_probe, + .remove = vf610_m4_rproc_remove, + .driver = { + .name = "vf610_m4_rproc", + .of_match_table = vf610_m4_rproc_of_match, + .pm = &vf610_m4_rproc_pm_ops, + }, +}; +module_platform_driver(vf610_m4_rproc_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Freescale Vybrid Cortex-M4 Remote Processor driver"); +MODULE_AUTHOR("Stefan Agner <stefan.agner@toradex.com>"); diff --git a/drivers/rpmsg/Kconfig b/drivers/rpmsg/Kconfig index 69a219387582..4f42214bf2d6 100644 --- a/drivers/rpmsg/Kconfig +++ b/drivers/rpmsg/Kconfig @@ -6,4 +6,16 @@ config RPMSG select VIRTIO select VIRTUALIZATION +config IMX_RPMSG_PINGPONG + tristate "IMX RPMSG pingpong driver -- loadable modules only" + depends on RPMSG && m + +config IMX_RPMSG_TTY + tristate "IMX RPMSG tty driver -- loadable modules only" + depends on RPMSG && m + +config VF610_RPMSG + tristate "VF610 RPMSG driver -- loadable modules only" + depends on RPMSG && m + endmenu diff --git a/drivers/rpmsg/Makefile b/drivers/rpmsg/Makefile index 7617fcb8259f..c19b2f7b41c4 100644 --- a/drivers/rpmsg/Makefile +++ b/drivers/rpmsg/Makefile @@ -1 +1,4 @@ obj-$(CONFIG_RPMSG) += virtio_rpmsg_bus.o +obj-$(CONFIG_IMX_RPMSG_PINGPONG) += imx_rpmsg_pingpong.o +obj-$(CONFIG_IMX_RPMSG_TTY) += imx_rpmsg_tty.o +obj-$(CONFIG_VF610_RPMSG) += vf610_rpmsg.o diff --git a/drivers/rpmsg/imx_rpmsg_pingpong.c b/drivers/rpmsg/imx_rpmsg_pingpong.c new file mode 100644 index 000000000000..a6843b360e80 --- /dev/null +++ b/drivers/rpmsg/imx_rpmsg_pingpong.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. + * + * derived from the omap-rpmsg implementation. + * Remote processor messaging transport - pingpong driver + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/virtio.h> +#include <linux/rpmsg.h> + +#define MSG "hello world!" +#define MSG_LIMIT 1000 +static unsigned int rpmsg_pingpong; +static int rx_count; + +static void rpmsg_pingpong_cb(struct rpmsg_channel *rpdev, void *data, int len, + void *priv, u32 src) +{ + int err; + + /* reply */ + rpmsg_pingpong = *(unsigned int *)data; + pr_info("get %d (src: 0x%x)\n", + rpmsg_pingpong, src); + rx_count++; + + /* pingpongs should not live forever */ + if (rx_count >= MSG_LIMIT) { + dev_info(&rpdev->dev, "goodbye!\n"); + return; + } + rpmsg_pingpong++; + err = rpmsg_sendto(rpdev, (void *)(&rpmsg_pingpong), 4, src); + + if (err) + dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", err); +} + +static int rpmsg_pingpong_probe(struct rpmsg_channel *rpdev) +{ + int err; + + dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n", + rpdev->src, rpdev->dst); + + /* + * send a message to our remote processor, and tell remote + * processor about this channel + */ + err = rpmsg_send(rpdev, MSG, strlen(MSG)); + if (err) { + dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", err); + return err; + } + + rpmsg_pingpong = 0; + rx_count = 0; + err = rpmsg_sendto(rpdev, (void *)(&rpmsg_pingpong), 4, rpdev->dst); + if (err) { + dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", err); + return err; + } + + return 0; +} + +static void rpmsg_pingpong_remove(struct rpmsg_channel *rpdev) +{ + dev_info(&rpdev->dev, "rpmsg pingpong driver is removed\n"); +} + +static struct rpmsg_device_id rpmsg_driver_pingpong_id_table[] = { + { .name = "rpmsg-openamp-demo-channel" }, + { }, +}; +MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_pingpong_id_table); + +static struct rpmsg_driver rpmsg_pingpong_driver = { + .drv.name = KBUILD_MODNAME, + .drv.owner = THIS_MODULE, + .id_table = rpmsg_driver_pingpong_id_table, + .probe = rpmsg_pingpong_probe, + .callback = rpmsg_pingpong_cb, + .remove = rpmsg_pingpong_remove, +}; + +static int __init init(void) +{ + return register_rpmsg_driver(&rpmsg_pingpong_driver); +} + +static void __exit fini(void) +{ + unregister_rpmsg_driver(&rpmsg_pingpong_driver); +} +module_init(init); +module_exit(fini); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("iMX virtio remote processor messaging pingpong driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/rpmsg/imx_rpmsg_tty.c b/drivers/rpmsg/imx_rpmsg_tty.c new file mode 100644 index 000000000000..14904c7d517e --- /dev/null +++ b/drivers/rpmsg/imx_rpmsg_tty.c @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. + * + * derived from the omap-rpmsg implementation. + * Remote processor messaging transport - tty driver + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/rpmsg.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/virtio.h> + +/* + * struct rpmsgtty_port - Wrapper struct for imx rpmsg tty port. + * @port: TTY port data + */ +struct rpmsgtty_port { + struct tty_port port; + spinlock_t rx_lock; + struct rpmsg_channel *rpdev; +}; + +static struct rpmsgtty_port rpmsg_tty_port; + +#define RPMSG_MAX_SIZE (512 - sizeof(struct rpmsg_hdr)) +#define MSG "hello world!" + +static void rpmsg_tty_cb(struct rpmsg_channel *rpdev, void *data, int len, + void *priv, u32 src) +{ + int space; + unsigned char *cbuf; + struct rpmsgtty_port *cport = &rpmsg_tty_port; + + /* flush the recv-ed none-zero data to tty node */ + if (len == 0) + return; + + dev_dbg(&rpdev->dev, "msg(<- src 0x%x) len %d\n", src, len); + + print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1, + data, len, true); + + spin_lock_bh(&cport->rx_lock); + space = tty_prepare_flip_string(&cport->port, &cbuf, len); + if (space <= 0) { + dev_err(&rpdev->dev, "No memory for tty_prepare_flip_string\n"); + spin_unlock_bh(&cport->rx_lock); + return; + } + + memcpy(cbuf, data, len); + tty_flip_buffer_push(&cport->port); + spin_unlock_bh(&cport->rx_lock); +} + +static struct tty_port_operations rpmsgtty_port_ops = { }; + +static int rpmsgtty_install(struct tty_driver *driver, struct tty_struct *tty) +{ + return tty_port_install(&rpmsg_tty_port.port, driver, tty); +} + +static int rpmsgtty_open(struct tty_struct *tty, struct file *filp) +{ + return tty_port_open(tty->port, tty, filp); +} + +static void rpmsgtty_close(struct tty_struct *tty, struct file *filp) +{ + return tty_port_close(tty->port, tty, filp); +} + +static int rpmsgtty_write(struct tty_struct *tty, const unsigned char *buf, + int total) +{ + int count, ret = 0; + const unsigned char *tbuf; + struct rpmsgtty_port *rptty_port = container_of(tty->port, + struct rpmsgtty_port, port); + struct rpmsg_channel *rpdev = rptty_port->rpdev; + + if (NULL == buf) { + pr_err("buf shouldn't be null.\n"); + return -ENOMEM; + } + + count = total; + tbuf = buf; + do { + /* send a message to our remote processor */ + ret = rpmsg_send(rpdev, (void *)tbuf, + count > RPMSG_MAX_SIZE ? RPMSG_MAX_SIZE : count); + if (ret) { + dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret); + return ret; + } + + if (count > RPMSG_MAX_SIZE) { + count -= RPMSG_MAX_SIZE; + tbuf += RPMSG_MAX_SIZE; + } else { + count = 0; + } + } while (count > 0); + + return total; +} + +static int rpmsgtty_write_room(struct tty_struct *tty) +{ + /* report the space in the rpmsg buffer */ + return RPMSG_MAX_SIZE; +} + +static const struct tty_operations imxrpmsgtty_ops = { + .install = rpmsgtty_install, + .open = rpmsgtty_open, + .close = rpmsgtty_close, + .write = rpmsgtty_write, + .write_room = rpmsgtty_write_room, +}; + +static struct tty_driver *rpmsgtty_driver; + +static int rpmsg_tty_probe(struct rpmsg_channel *rpdev) +{ + int err; + struct rpmsgtty_port *cport = &rpmsg_tty_port; + + dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n", + rpdev->src, rpdev->dst); + + /* + * send a message to our remote processor, and tell remote + * processor about this channel + */ + err = rpmsg_send(rpdev, MSG, strlen(MSG)); + if (err) { + dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", err); + return err; + } + + rpmsgtty_driver = tty_alloc_driver(1, TTY_DRIVER_UNNUMBERED_NODE); + if (IS_ERR(rpmsgtty_driver)) + return PTR_ERR(rpmsgtty_driver); + + rpmsgtty_driver->driver_name = "rpmsg_tty"; + rpmsgtty_driver->name = "ttyRPMSG"; + rpmsgtty_driver->major = TTYAUX_MAJOR; + rpmsgtty_driver->minor_start = 3; + rpmsgtty_driver->type = TTY_DRIVER_TYPE_CONSOLE; + rpmsgtty_driver->init_termios = tty_std_termios; + + tty_set_operations(rpmsgtty_driver, &imxrpmsgtty_ops); + + tty_port_init(&cport->port); + cport->port.ops = &rpmsgtty_port_ops; + spin_lock_init(&cport->rx_lock); + cport->port.low_latency = cport->port.flags | ASYNC_LOW_LATENCY; + + err = tty_register_driver(rpmsgtty_driver); + if (err < 0) { + pr_err("Couldn't install rpmsg tty driver: err %d\n", err); + goto error; + } else + pr_info("Install rpmsg tty driver!\n"); + cport->rpdev = rpdev; + + return 0; + +error: + tty_unregister_driver(rpmsgtty_driver); + put_tty_driver(rpmsgtty_driver); + tty_port_destroy(&cport->port); + rpmsgtty_driver = NULL; + + return err; +} + +static void rpmsg_tty_remove(struct rpmsg_channel *rpdev) +{ + struct rpmsgtty_port *cport = &rpmsg_tty_port; + + dev_info(&rpdev->dev, "rpmsg tty driver is removed\n"); + + tty_unregister_driver(rpmsgtty_driver); + put_tty_driver(rpmsgtty_driver); + tty_port_destroy(&cport->port); + rpmsgtty_driver = NULL; +} + +static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = { + { .name = "rpmsg-openamp-demo-channel" }, + { }, +}; +MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table); + +static struct rpmsg_driver rpmsg_tty_driver = { + .drv.name = KBUILD_MODNAME, + .drv.owner = THIS_MODULE, + .id_table = rpmsg_driver_tty_id_table, + .probe = rpmsg_tty_probe, + .callback = rpmsg_tty_cb, + .remove = rpmsg_tty_remove, +}; + +static int __init init(void) +{ + return register_rpmsg_driver(&rpmsg_tty_driver); +} + +static void __exit fini(void) +{ + unregister_rpmsg_driver(&rpmsg_tty_driver); +} +module_init(init); +module_exit(fini); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("iMX virtio remote processor messaging tty driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/rpmsg/vf610_rpmsg.c b/drivers/rpmsg/vf610_rpmsg.c new file mode 100644 index 000000000000..72ce28c7d13e --- /dev/null +++ b/drivers/rpmsg/vf610_rpmsg.c @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2016 Toradex AG + * + * Derived from the downstream iMX7 rpmsg implementation by Freescale. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/vf610_mscm.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/rpmsg.h> +#include <linux/slab.h> +#include <linux/vf610_sema4.h> +#include <linux/virtio.h> +#include <linux/virtio_config.h> +#include <linux/virtio_ids.h> +#include <linux/virtio_ring.h> + +struct vf610_rpmsg_vproc { + struct virtio_device vdev; + unsigned int vring[2]; + char *rproc_name; + struct mutex lock; + struct delayed_work rpmsg_work; + struct virtqueue *vq[2]; + int base_vq_id; + int num_of_vqs; +}; + +#define MSCM_CPU_M4 1 + +/* + * For now, allocate 256 buffers of 512 bytes for each side. each buffer + * will then have 16B for the msg header and 496B for the payload. + * This will require a total space of 256KB for the buffers themselves, and + * 3 pages for every vring (the size of the vring depends on the number of + * buffers it supports). + */ +#define RPMSG_NUM_BUFS (32) +#define RPMSG_BUF_SIZE (512) +#define RPMSG_BUFS_SPACE (RPMSG_NUM_BUFS * RPMSG_BUF_SIZE) + +/* + * The alignment between the consumer and producer parts of the vring. + * Note: this is part of the "wire" protocol. If you change this, you need + * to update your BIOS image as well + */ +#define RPMSG_VRING_ALIGN (4096) + +/* With 256 buffers, our vring will occupy 3 pages */ +#define RPMSG_RING_SIZE ((DIV_ROUND_UP(vring_size(RPMSG_NUM_BUFS / 2, \ + RPMSG_VRING_ALIGN), PAGE_SIZE)) * PAGE_SIZE) + +#define to_vf610_rpdev(vd) container_of(vd, struct vf610_rpmsg_vproc, vdev) + +struct vf610_rpmsg_vq_info { + __u16 num; /* number of entries in the virtio_ring */ + __u16 vq_id; /* a globaly unique index of this virtqueue */ + void *addr; /* address where we mapped the virtio ring */ + struct vf610_rpmsg_vproc *rpdev; +}; + +static struct vf610_sema4_mutex *rpmsg_mutex; + +static u64 vf610_rpmsg_get_features(struct virtio_device *vdev) +{ + return 1 << VIRTIO_RPMSG_F_NS; +} + +static int vf610_rpmsg_finalize_features(struct virtio_device *vdev) +{ + /* Give virtio_ring a chance to accept features */ + vring_transport_features(vdev); + + return 0; +} + +/* kick the remote processor */ +static bool vf610_rpmsg_notify(struct virtqueue *vq) +{ + struct vf610_rpmsg_vq_info *rpvq = vq->priv; + + mutex_lock(&rpvq->rpdev->lock); + + mscm_trigger_cpu2cpu_irq(rpvq->vq_id, MSCM_CPU_M4); + + mutex_unlock(&rpvq->rpdev->lock); + + return true; +} + +static void rpmsg_work_handler(struct work_struct *work) +{ + struct vf610_rpmsg_vproc *rpdev = container_of(work, + struct vf610_rpmsg_vproc, rpmsg_work.work); + + /* Process incoming buffers on all our vrings */ + vring_interrupt(0, rpdev->vq[0]); + vring_interrupt(1, rpdev->vq[1]); +} + +static irqreturn_t cpu_to_cpu_irq_handler(int irq, void *p) +{ + struct vf610_rpmsg_vproc *rpdev = (struct vf610_rpmsg_vproc *)p; + + schedule_delayed_work(&rpdev->rpmsg_work, 0); + + return IRQ_HANDLED; +} + +static struct virtqueue *rp_find_vq(struct virtio_device *vdev, + unsigned index, + void (*callback)(struct virtqueue *vq), + const char *name) +{ + struct vf610_rpmsg_vproc *rpdev = to_vf610_rpdev(vdev); + struct vf610_rpmsg_vq_info *rpvq; + struct virtqueue *vq; + int err; + + rpvq = kmalloc(sizeof(*rpvq), GFP_KERNEL); + if (!rpvq) + return ERR_PTR(-ENOMEM); + + /* ioremap'ing normal memory, so we cast away sparse's complaints */ + rpvq->addr = (__force void *) ioremap_nocache(rpdev->vring[index], + RPMSG_RING_SIZE); + if (!rpvq->addr) { + err = -ENOMEM; + goto free_rpvq; + } + + memset(rpvq->addr, 0, RPMSG_RING_SIZE); + + pr_debug("vring%d: phys 0x%x, virt 0x%x\n", index, rpdev->vring[index], + (unsigned int) rpvq->addr); + + vq = vring_new_virtqueue(index, RPMSG_NUM_BUFS, RPMSG_VRING_ALIGN, + vdev, true, rpvq->addr, vf610_rpmsg_notify, callback, + name); + if (!vq) { + pr_err("vring_new_virtqueue failed\n"); + err = -ENOMEM; + goto unmap_vring; + } + + rpdev->vq[index] = vq; + vq->priv = rpvq; + /* system-wide unique id for this virtqueue */ + rpvq->vq_id = rpdev->base_vq_id + index; + rpvq->rpdev = rpdev; + mutex_init(&rpdev->lock); + + return vq; + +unmap_vring: + /* iounmap normal memory, so make sparse happy */ + iounmap((__force void __iomem *) rpvq->addr); +free_rpvq: + kfree(rpvq); + return ERR_PTR(err); +} + +static void vf610_rpmsg_del_vqs(struct virtio_device *vdev) +{ + struct virtqueue *vq, *n; + + list_for_each_entry_safe(vq, n, &vdev->vqs, list) { + struct vf610_rpmsg_vq_info *rpvq = vq->priv; + iounmap(rpvq->addr); + vring_del_virtqueue(vq); + kfree(rpvq); + } +} + +static int vf610_rpmsg_find_vqs(struct virtio_device *vdev, unsigned nvqs, + struct virtqueue *vqs[], + vq_callback_t *callbacks[], + const char *names[]) +{ + struct vf610_rpmsg_vproc *rpdev = to_vf610_rpdev(vdev); + int i, err; + + /* we maintain two virtqueues per remote processor (for RX and TX) */ + if (nvqs != 2) + return -EINVAL; + + for (i = 0; i < nvqs; ++i) { + vqs[i] = rp_find_vq(vdev, i, callbacks[i], names[i]); + if (IS_ERR(vqs[i])) { + err = PTR_ERR(vqs[i]); + goto error; + } + } + + rpdev->num_of_vqs = nvqs; + + return 0; + +error: + vf610_rpmsg_del_vqs(vdev); + return err; +} + +static void vf610_rpmsg_reset(struct virtio_device *vdev) +{ + dev_dbg(&vdev->dev, "reset !\n"); +} + +static u8 vf610_rpmsg_get_status(struct virtio_device *vdev) +{ + return 0; +} + +static void vf610_rpmsg_set_status(struct virtio_device *vdev, u8 status) +{ + dev_dbg(&vdev->dev, "%s new status: %d\n", __func__, status); +} + +static void vf610_rpmsg_vproc_release(struct device *dev) +{ + /* this handler is provided so driver core doesn't yell at us */ +} + +static struct virtio_config_ops vf610_rpmsg_config_ops = { + .get_features = vf610_rpmsg_get_features, + .finalize_features = vf610_rpmsg_finalize_features, + .find_vqs = vf610_rpmsg_find_vqs, + .del_vqs = vf610_rpmsg_del_vqs, + .reset = vf610_rpmsg_reset, + .set_status = vf610_rpmsg_set_status, + .get_status = vf610_rpmsg_get_status, +}; + +static struct vf610_rpmsg_vproc vf610_rpmsg_vprocs = { + .vdev.id.device = VIRTIO_ID_RPMSG, + .vdev.config = &vf610_rpmsg_config_ops, + .rproc_name = "vf610_m4", + .base_vq_id = 0, +}; + +static const struct of_device_id vf610_rpmsg_dt_ids[] = { + { .compatible = "fsl,vf610-rpmsg", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vf610_rpmsg_dt_ids); + +static int vf610_rpmsg_probe(struct platform_device *pdev) +{ + int i; + int ret = 0; + struct vf610_rpmsg_vproc *rpdev = &vf610_rpmsg_vprocs; + + INIT_DELAYED_WORK(&rpdev->rpmsg_work, rpmsg_work_handler); + + rpdev->vring[0] = 0x3f070000; + rpdev->vring[1] = 0x3f074000; + + rpdev->vdev.dev.parent = &pdev->dev; + rpdev->vdev.dev.release = vf610_rpmsg_vproc_release; + + ret = register_virtio_device(&rpdev->vdev); + if (ret) { + pr_err("%s failed to register rpdev: %d\n", + __func__, ret); + return ret; + } + + for (i = 0; i < 2; i++) { + ret = mscm_request_cpu2cpu_irq(i, cpu_to_cpu_irq_handler, + (const char *)rpdev->rproc_name, rpdev); + if (ret) { + dev_err(&pdev->dev, + "Failed to register CPU2CPU interrupt\n"); + unregister_virtio_device(&rpdev->vdev); + return ret; + } + } + + return 0; +} + +static int vf610_rpmsg_remove(struct platform_device *pdev) +{ + struct vf610_rpmsg_vproc *rpdev = &vf610_rpmsg_vprocs; + int i; + + for (i = 0; i < 2; i++) + mscm_free_cpu2cpu_irq(i, NULL); + + unregister_virtio_device(&rpdev->vdev); + + return 0; +} + +static struct platform_driver vf610_rpmsg_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "vf610-rpmsg", + .of_match_table = vf610_rpmsg_dt_ids, + }, + .probe = vf610_rpmsg_probe, + .remove = vf610_rpmsg_remove, +}; + +static int __init vf610_rpmsg_init(void) +{ + int ret; + + rpmsg_mutex = vf610_sema4_mutex_create(0, 0); + if (IS_ERR(rpmsg_mutex)) { + pr_err("vf610 rpmsg unable to create mutex\n"); + return PTR_ERR(rpmsg_mutex); + } + + vf610_sema4_mutex_lock(rpmsg_mutex); + + ret = platform_driver_register(&vf610_rpmsg_driver); + if (ret) + pr_err("Unable to initialize rpmsg driver\n"); + else + pr_info("vf610 rpmsg driver is registered.\n"); + + vf610_sema4_mutex_unlock(rpmsg_mutex); + + return ret; +} + +static void __exit vf610_rpmsg_exit(void) +{ + if (rpmsg_mutex) + vf610_sema4_mutex_destroy(rpmsg_mutex); + + pr_info("vf610 rpmsg driver is unregistered.\n"); + platform_driver_unregister(&vf610_rpmsg_driver); +} +module_exit(vf610_rpmsg_exit); +module_init(vf610_rpmsg_init); + +MODULE_AUTHOR("Sanchayan Maity <sanchayan.maity@toradex.com>"); +MODULE_DESCRIPTION("vf610 remote processor messaging virtio device"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/rtc/rtc-ds1307.c b/drivers/rtc/rtc-ds1307.c index aa705bb4748c..097ca2a8565f 100644 --- a/drivers/rtc/rtc-ds1307.c +++ b/drivers/rtc/rtc-ds1307.c @@ -34,6 +34,7 @@ enum ds_type { ds_1340, ds_1388, ds_3231, + m41t0, m41t00, mcp794xx, rx_8025, @@ -48,6 +49,7 @@ enum ds_type { # define DS1340_BIT_nEOSC 0x80 # define MCP794XX_BIT_ST 0x80 #define DS1307_REG_MIN 0x01 /* 00-59 */ +# define M41T0_BIT_OF 0x80 #define DS1307_REG_HOUR 0x02 /* 00-23, or 1-12{am,pm} */ # define DS1307_BIT_12HR 0x40 /* in REG_HOUR */ # define DS1307_BIT_PM 0x20 /* in REG_HOUR */ @@ -174,6 +176,7 @@ static const struct i2c_device_id ds1307_id[] = { { "ds1388", ds_1388 }, { "ds1340", ds_1340 }, { "ds3231", ds_3231 }, + { "m41t0", m41t0 }, { "m41t00", m41t00 }, { "mcp7940x", mcp794xx }, { "mcp7941x", mcp794xx }, @@ -363,6 +366,13 @@ static int ds1307_get_time(struct device *dev, struct rtc_time *t) dev_dbg(dev, "%s: %7ph\n", "read", ds1307->regs); + /* if oscillator fail bit is set, no data can be trusted */ + if (ds1307->type == m41t0 && + ds1307->regs[DS1307_REG_MIN] & M41T0_BIT_OF) { + dev_warn_once(dev, "oscillator failed, set time!\n"); + return -EINVAL; + } + t->tm_sec = bcd2bin(ds1307->regs[DS1307_REG_SECS] & 0x7f); t->tm_min = bcd2bin(ds1307->regs[DS1307_REG_MIN] & 0x7f); tmp = ds1307->regs[DS1307_REG_HOUR] & 0x3f; @@ -1044,6 +1054,7 @@ read_rtc: tmp = ds1307->regs[DS1307_REG_SECS]; switch (ds1307->type) { case ds_1307: + case m41t0: case m41t00: /* clock halted? turn it on, so clock can tick. */ if (tmp & DS1307_BIT_CH) { @@ -1108,6 +1119,7 @@ read_rtc: tmp = ds1307->regs[DS1307_REG_HOUR]; switch (ds1307->type) { case ds_1340: + case m41t0: case m41t00: /* * NOTE: ignores century bits; fix before deploying diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig index 4e853ed2c82b..9864fd55a0ab 100644 --- a/drivers/soc/Kconfig +++ b/drivers/soc/Kconfig @@ -1,6 +1,7 @@ menu "SOC (System On Chip) specific Drivers" source "drivers/soc/brcmstb/Kconfig" +source "drivers/soc/fsl/Kconfig" source "drivers/soc/mediatek/Kconfig" source "drivers/soc/qcom/Kconfig" source "drivers/soc/rockchip/Kconfig" diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index f2ba2e932ae1..21c9c3a0c98f 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -10,4 +10,5 @@ obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ obj-$(CONFIG_ARCH_SUNXI) += sunxi/ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-$(CONFIG_SOC_TI) += ti/ +obj-$(CONFIG_SOC_VF610) += fsl/ obj-$(CONFIG_PLAT_VERSATILE) += versatile/ diff --git a/drivers/soc/fsl/Kconfig b/drivers/soc/fsl/Kconfig new file mode 100644 index 000000000000..5568c34474f7 --- /dev/null +++ b/drivers/soc/fsl/Kconfig @@ -0,0 +1,10 @@ +# +# Freescale SoC drivers + +config SOC_BUS_VF610 + tristate "SoC bus device for the Freescale Vybrid platform" + depends on SOC_VF610 && NVMEM && NVMEM_VF610_OCOTP + select SOC_BUS + help + Include support for the SoC bus on the Freescale Vybrid platform + providing some sysfs information about the module variant. diff --git a/drivers/soc/fsl/Makefile b/drivers/soc/fsl/Makefile new file mode 100644 index 000000000000..41ee22fe311f --- /dev/null +++ b/drivers/soc/fsl/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SOC_BUS_VF610) += soc-vf610.o diff --git a/drivers/soc/fsl/soc-vf610.c b/drivers/soc/fsl/soc-vf610.c new file mode 100644 index 000000000000..1db42c7442f6 --- /dev/null +++ b/drivers/soc/fsl/soc-vf610.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2016 Toradex AG. + * + * Author: Sanchayan Maity <sanchayan.maity@toradex.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ + +#include <linux/device.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/nvmem-consumer.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/random.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/sys_soc.h> + +struct vf610_soc { + struct device *dev; + struct soc_device_attribute *soc_dev_attr; + struct soc_device *soc_dev; + struct nvmem_cell *ocotp_cfg0; + struct nvmem_cell *ocotp_cfg1; +}; + +static int vf610_soc_probe(struct platform_device *pdev) +{ + struct vf610_soc *info; + struct device *dev = &pdev->dev; + struct device_node *soc_node; + char soc_type[] = "xx0"; + size_t id1_len; + size_t id2_len; + u32 cpucount; + u32 l2size; + u32 rom_rev; + u8 *socid1; + u8 *socid2; + char *socid; + int ret; + + info = devm_kzalloc(dev, sizeof(struct vf610_soc), GFP_KERNEL); + if (!info) + return -ENOMEM; + info->dev = dev; + + info->ocotp_cfg0 = devm_nvmem_cell_get(dev, "cfg0"); + if (IS_ERR(info->ocotp_cfg0)) + return -EPROBE_DEFER; + + info->ocotp_cfg1 = devm_nvmem_cell_get(dev, "cfg1"); + if (IS_ERR(info->ocotp_cfg1)) + return -EPROBE_DEFER; + + socid1 = nvmem_cell_read(info->ocotp_cfg0, &id1_len); + if (IS_ERR(socid1)) { + dev_err(dev, "Could not read nvmem cell %ld\n", + PTR_ERR(socid1)); + return PTR_ERR(socid1); + } + + socid2 = nvmem_cell_read(info->ocotp_cfg1, &id2_len); + if (IS_ERR(socid2)) { + dev_err(dev, "Could not read nvmem cell %ld\n", + PTR_ERR(socid2)); + kfree(socid1); + return PTR_ERR(socid2); + } + add_device_randomness(socid1, id1_len); + add_device_randomness(socid2, id2_len); + + socid = devm_kasprintf(dev, GFP_KERNEL, + "%02x%02x%02x%02x%02x%02x%02x%02x", + socid1[3], socid1[2], socid1[1], socid1[0], + socid2[3], socid2[2], socid2[1], socid2[0]); + + kfree(socid1); + kfree(socid2); + + + soc_node = of_find_node_by_path("/soc"); + if (soc_node == NULL) + return -ENODEV; + + ret = syscon_regmap_read_from_offset(soc_node, + "fsl,rom-revision", &rom_rev); + if (ret) { + of_node_put(soc_node); + return ret; + } + + ret = syscon_regmap_read_from_offset(soc_node, + "fsl,cpu-count", &cpucount); + if (ret) { + of_node_put(soc_node); + return ret; + } + + ret = syscon_regmap_read_from_offset(soc_node, + "fsl,l2-size", &l2size); + if (ret) { + of_node_put(soc_node); + return ret; + } + + of_node_put(soc_node); + + soc_type[0] = cpucount ? '6' : '5'; /* Dual Core => VF6x0 */ + soc_type[1] = l2size ? '1' : '0'; /* L2 Cache => VFx10 */ + + info->soc_dev_attr = devm_kzalloc(dev, + sizeof(info->soc_dev_attr), GFP_KERNEL); + if (!info->soc_dev_attr) + return -ENOMEM; + + info->soc_dev_attr->machine = devm_kasprintf(dev, + GFP_KERNEL, "Freescale Vybrid"); + info->soc_dev_attr->soc_id = socid; + info->soc_dev_attr->family = devm_kasprintf(&pdev->dev, + GFP_KERNEL, "Freescale Vybrid VF%s", + soc_type); + info->soc_dev_attr->revision = devm_kasprintf(dev, + GFP_KERNEL, "%08x", rom_rev); + + platform_set_drvdata(pdev, info); + + info->soc_dev = soc_device_register(info->soc_dev_attr); + if (IS_ERR(info->soc_dev)) + return -ENODEV; + + return 0; +} + +static int vf610_soc_remove(struct platform_device *pdev) +{ + struct vf610_soc *info = platform_get_drvdata(pdev); + + if (info->soc_dev) + soc_device_unregister(info->soc_dev); + + return 0; +} + +static const struct of_device_id vf610_soc_bus_match[] = { + { .compatible = "fsl,vf610-soc-bus", }, + { /* */ } +}; + +static struct platform_driver vf610_soc_driver = { + .probe = vf610_soc_probe, + .remove = vf610_soc_remove, + .driver = { + .name = "vf610-soc-bus", + .of_match_table = vf610_soc_bus_match, + }, +}; +module_platform_driver(vf610_soc_driver); diff --git a/drivers/spi/spi-fsl-dspi.c b/drivers/spi/spi-fsl-dspi.c index a3965cac1b34..e280c6179e57 100644 --- a/drivers/spi/spi-fsl-dspi.c +++ b/drivers/spi/spi-fsl-dspi.c @@ -15,6 +15,8 @@ #include <linux/clk.h> #include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> #include <linux/err.h> #include <linux/errno.h> #include <linux/interrupt.h> @@ -40,6 +42,7 @@ #define TRAN_STATE_WORD_ODD_NUM 0x04 #define DSPI_FIFO_SIZE 4 +#define DSPI_DMA_BUFSIZE (DSPI_FIFO_SIZE * 1024) #define SPI_MCR 0x00 #define SPI_MCR_MASTER (1 << 31) @@ -71,6 +74,11 @@ #define SPI_SR_EOQF 0x10000000 #define SPI_SR_TCFQF 0x80000000 +#define SPI_RSER_TFFFE BIT(25) +#define SPI_RSER_TFFFD BIT(24) +#define SPI_RSER_RFDFE BIT(17) +#define SPI_RSER_RFDFD BIT(16) + #define SPI_RSER 0x30 #define SPI_RSER_EOQFE 0x10000000 #define SPI_RSER_TCFQE 0x80000000 @@ -108,6 +116,8 @@ #define SPI_TCR_TCNT_MAX 0x10000 +#define DMA_COMPLETION_TIMEOUT msecs_to_jiffies(3000) + struct chip_data { u32 mcr_val; u32 ctar_val; @@ -117,22 +127,43 @@ struct chip_data { enum dspi_trans_mode { DSPI_EOQ_MODE = 0, DSPI_TCFQ_MODE, + DSPI_DMA_MODE, }; struct fsl_dspi_devtype_data { enum dspi_trans_mode trans_mode; + u8 max_clock_factor; }; static const struct fsl_dspi_devtype_data vf610_data = { .trans_mode = DSPI_EOQ_MODE, + .max_clock_factor = 2, }; static const struct fsl_dspi_devtype_data ls1021a_v1_data = { .trans_mode = DSPI_TCFQ_MODE, + .max_clock_factor = 8, }; static const struct fsl_dspi_devtype_data ls2085a_data = { .trans_mode = DSPI_TCFQ_MODE, + .max_clock_factor = 8, +}; + +struct fsl_dspi_dma { + u32 curr_xfer_len; + + u32 *tx_dma_buf; + struct dma_chan *chan_tx; + dma_addr_t tx_dma_phys; + struct completion cmd_tx_complete; + struct dma_async_tx_descriptor *tx_desc; + + u32 *rx_dma_buf; + struct dma_chan *chan_rx; + dma_addr_t rx_dma_phys; + struct completion cmd_rx_complete; + struct dma_async_tx_descriptor *rx_desc; }; struct fsl_dspi { @@ -161,8 +192,11 @@ struct fsl_dspi { u32 waitflags; u32 spi_tcnt; + struct fsl_dspi_dma *dma; }; +static u32 dspi_data_to_pushr(struct fsl_dspi *dspi, int tx_word); + static inline int is_double_byte_mode(struct fsl_dspi *dspi) { unsigned int val; @@ -364,6 +398,256 @@ static void dspi_tcfq_read(struct fsl_dspi *dspi) dspi_data_from_popr(dspi, rx_word); } +static void dspi_tx_dma_callback(void *arg) +{ + struct fsl_dspi *dspi = arg; + struct fsl_dspi_dma *dma = dspi->dma; + + complete(&dma->cmd_tx_complete); +} + +static void dspi_rx_dma_callback(void *arg) +{ + struct fsl_dspi *dspi = arg; + struct fsl_dspi_dma *dma = dspi->dma; + int rx_word; + int i; + u16 d; + + rx_word = is_double_byte_mode(dspi); + + if (!(dspi->dataflags & TRAN_STATE_RX_VOID)) { + for (i = 0; i < dma->curr_xfer_len; i++) { + d = dspi->dma->rx_dma_buf[i]; + rx_word ? (*(u16 *)dspi->rx = d) : + (*(u8 *)dspi->rx = d); + dspi->rx += rx_word + 1; + } + } + + complete(&dma->cmd_rx_complete); +} + +static int dspi_next_xfer_dma_submit(struct fsl_dspi *dspi) +{ + struct fsl_dspi_dma *dma = dspi->dma; + struct device *dev = &dspi->pdev->dev; + int time_left; + int tx_word; + int i; + + tx_word = is_double_byte_mode(dspi); + + for (i = 0; i < dma->curr_xfer_len; i++) { + dspi->dma->tx_dma_buf[i] = dspi_data_to_pushr(dspi, tx_word); + if ((dspi->cs_change) && (!dspi->len)) + dspi->dma->tx_dma_buf[i] &= ~SPI_PUSHR_CONT; + } + + dma->tx_desc = dmaengine_prep_slave_single(dma->chan_tx, + dma->tx_dma_phys, + dma->curr_xfer_len * + DMA_SLAVE_BUSWIDTH_4_BYTES, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!dma->tx_desc) { + dev_err(dev, "Not able to get desc for DMA xfer\n"); + return -EIO; + } + + dma->tx_desc->callback = dspi_tx_dma_callback; + dma->tx_desc->callback_param = dspi; + if (dma_submit_error(dmaengine_submit(dma->tx_desc))) { + dev_err(dev, "DMA submit failed\n"); + return -EINVAL; + } + + dma->rx_desc = dmaengine_prep_slave_single(dma->chan_rx, + dma->rx_dma_phys, + dma->curr_xfer_len * + DMA_SLAVE_BUSWIDTH_4_BYTES, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!dma->rx_desc) { + dev_err(dev, "Not able to get desc for DMA xfer\n"); + return -EIO; + } + + dma->rx_desc->callback = dspi_rx_dma_callback; + dma->rx_desc->callback_param = dspi; + if (dma_submit_error(dmaengine_submit(dma->rx_desc))) { + dev_err(dev, "DMA submit failed\n"); + return -EINVAL; + } + + reinit_completion(&dspi->dma->cmd_rx_complete); + reinit_completion(&dspi->dma->cmd_tx_complete); + + dma_async_issue_pending(dma->chan_rx); + dma_async_issue_pending(dma->chan_tx); + + time_left = wait_for_completion_timeout(&dspi->dma->cmd_tx_complete, + DMA_COMPLETION_TIMEOUT); + if (time_left == 0) { + dev_err(dev, "DMA tx timeout\n"); + dmaengine_terminate_all(dma->chan_tx); + dmaengine_terminate_all(dma->chan_rx); + return -ETIMEDOUT; + } + + time_left = wait_for_completion_timeout(&dspi->dma->cmd_rx_complete, + DMA_COMPLETION_TIMEOUT); + if (time_left == 0) { + dev_err(dev, "DMA rx timeout\n"); + dmaengine_terminate_all(dma->chan_tx); + dmaengine_terminate_all(dma->chan_rx); + return -ETIMEDOUT; + } + + return 0; +} + +static int dspi_dma_xfer(struct fsl_dspi *dspi) +{ + struct fsl_dspi_dma *dma = dspi->dma; + struct device *dev = &dspi->pdev->dev; + int curr_remaining_bytes; + int bytes_per_buffer; + int word = 1; + int ret = 0; + + if(is_double_byte_mode(dspi)) + word = 2; + curr_remaining_bytes = dspi->len; + bytes_per_buffer = DSPI_DMA_BUFSIZE / DSPI_FIFO_SIZE; + while (curr_remaining_bytes) { + /* Check if current transfer fits the DMA buffer */ + dma->curr_xfer_len = curr_remaining_bytes / word; + if (dma->curr_xfer_len > bytes_per_buffer) + dma->curr_xfer_len = bytes_per_buffer; + + ret = dspi_next_xfer_dma_submit(dspi); + if (ret) { + dev_err(dev, "DMA transfer failed\n"); + goto exit; + + } else { + curr_remaining_bytes -= dma->curr_xfer_len * word; + if (curr_remaining_bytes < 0) + curr_remaining_bytes = 0; + } + } + +exit: + return ret; +} + +static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr) +{ + struct fsl_dspi_dma *dma; + struct dma_slave_config cfg; + struct device *dev = &dspi->pdev->dev; + int ret; + + dma = devm_kzalloc(dev, sizeof(*dma), GFP_KERNEL); + if (!dma) + return -ENOMEM; + + dma->chan_rx = dma_request_slave_channel(dev, "rx"); + if (!dma->chan_rx) { + dev_err(dev, "rx dma channel not available\n"); + ret = -ENODEV; + return ret; + } + + dma->chan_tx = dma_request_slave_channel(dev, "tx"); + if (!dma->chan_tx) { + dev_err(dev, "tx dma channel not available\n"); + ret = -ENODEV; + goto err_tx_channel; + } + + dma->tx_dma_buf = dma_alloc_coherent(dev, DSPI_DMA_BUFSIZE, + &dma->tx_dma_phys, GFP_KERNEL); + if (!dma->tx_dma_buf) { + ret = -ENOMEM; + goto err_tx_dma_buf; + } + + dma->rx_dma_buf = dma_alloc_coherent(dev, DSPI_DMA_BUFSIZE, + &dma->rx_dma_phys, GFP_KERNEL); + if (!dma->rx_dma_buf) { + ret = -ENOMEM; + goto err_rx_dma_buf; + } + + cfg.src_addr = phy_addr + SPI_POPR; + cfg.dst_addr = phy_addr + SPI_PUSHR; + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + cfg.src_maxburst = 1; + cfg.dst_maxburst = 1; + + cfg.direction = DMA_DEV_TO_MEM; + ret = dmaengine_slave_config(dma->chan_rx, &cfg); + if (ret) { + dev_err(dev, "can't configure rx dma channel\n"); + ret = -EINVAL; + goto err_slave_config; + } + + cfg.direction = DMA_MEM_TO_DEV; + ret = dmaengine_slave_config(dma->chan_tx, &cfg); + if (ret) { + dev_err(dev, "can't configure tx dma channel\n"); + ret = -EINVAL; + goto err_slave_config; + } + + dspi->dma = dma; + dspi->devtype_data->trans_mode = DSPI_DMA_MODE; + init_completion(&dma->cmd_tx_complete); + init_completion(&dma->cmd_rx_complete); + + return 0; + +err_slave_config: + dma_free_coherent(dev, DSPI_DMA_BUFSIZE, + dma->rx_dma_buf, dma->rx_dma_phys); +err_rx_dma_buf: + dma_free_coherent(dev, DSPI_DMA_BUFSIZE, + dma->tx_dma_buf, dma->rx_dma_phys); +err_tx_dma_buf: + dma_release_channel(dma->chan_tx); +err_tx_channel: + dma_release_channel(dma->chan_rx); + + devm_kfree(dev, dma); + dspi->dma = NULL; + + return ret; +} + +static void dspi_release_dma(struct fsl_dspi *dspi) +{ + struct fsl_dspi_dma *dma = dspi->dma; + struct device *dev = &dspi->pdev->dev; + + if (dma) { + if (dma->chan_tx) { + dma_unmap_single(dev, dma->tx_dma_phys, + DSPI_DMA_BUFSIZE, DMA_TO_DEVICE); + dma_release_channel(dma->chan_tx); + } + + if (dma->chan_rx) { + dma_unmap_single(dev, dma->rx_dma_phys, + DSPI_DMA_BUFSIZE, DMA_FROM_DEVICE); + dma_release_channel(dma->chan_rx); + } + } +} + static int dspi_transfer_one_message(struct spi_master *master, struct spi_message *message) { @@ -420,6 +704,12 @@ static int dspi_transfer_one_message(struct spi_master *master, regmap_write(dspi->regmap, SPI_RSER, SPI_RSER_TCFQE); dspi_tcfq_write(dspi); break; + case DSPI_DMA_MODE: + regmap_write(dspi->regmap, SPI_RSER, + SPI_RSER_TFFFE | SPI_RSER_TFFFD | + SPI_RSER_RFDFE | SPI_RSER_RFDFD); + status = dspi_dma_xfer(dspi); + break; default: dev_err(&dspi->pdev->dev, "unsupported trans_mode %u\n", trans_mode); @@ -427,9 +717,13 @@ static int dspi_transfer_one_message(struct spi_master *master, goto out; } - if (wait_event_interruptible(dspi->waitq, dspi->waitflags)) - dev_err(&dspi->pdev->dev, "wait transfer complete fail!\n"); - dspi->waitflags = 0; + if (dspi->devtype_data->trans_mode != DSPI_DMA_MODE) { + if (wait_event_interruptible(dspi->waitq, + dspi->waitflags)) + dev_err(&dspi->pdev->dev, + "wait transfer complete fail!\n"); + dspi->waitflags = 0; + } if (transfer->delay_usecs) udelay(transfer->delay_usecs); @@ -726,6 +1020,12 @@ static int dspi_probe(struct platform_device *pdev) } clk_prepare_enable(dspi->clk); + if (dspi_request_dma(dspi, res->start)) + dev_warn(&pdev->dev, "can't get dma channels\n"); + + master->max_speed_hz = + clk_get_rate(dspi->clk) / dspi->devtype_data->max_clock_factor; + init_waitqueue_head(&dspi->waitq); platform_set_drvdata(pdev, master); @@ -751,6 +1051,7 @@ static int dspi_remove(struct platform_device *pdev) struct fsl_dspi *dspi = spi_master_get_devdata(master); /* Disconnect from the SPI framework */ + dspi_release_dma(dspi); clk_disable_unprepare(dspi->clk); spi_unregister_master(dspi->master); diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index d0e7dfc647cf..a6786fb526e5 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -695,6 +695,7 @@ static struct class *spidev_class; static const struct of_device_id spidev_dt_ids[] = { { .compatible = "rohm,dh2228fv" }, { .compatible = "lineartechnology,ltc2488" }, + { .compatible = "toradex,evalspi" }, {}, }; MODULE_DEVICE_TABLE(of, spidev_dt_ids); diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index 1544a7cc76ff..9e5362a3c2d4 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -224,12 +224,16 @@ #define UARTWATER_TXWATER_OFF 0 #define UARTWATER_RXWATER_OFF 16 -#define FSL_UART_RX_DMA_BUFFER_SIZE 64 +/* Rx DMA timeout in ms, which is used to calculate Rx ring buffer size */ +#define DMA_RX_TIMEOUT (10) #define DRIVER_NAME "fsl-lpuart" #define DEV_NAME "ttyLP" #define UART_NR 6 +static bool nodma = false; +module_param(nodma, bool, S_IRUGO); + struct lpuart_port { struct uart_port port; struct clk *clk; @@ -243,18 +247,18 @@ struct lpuart_port { struct dma_chan *dma_rx_chan; struct dma_async_tx_descriptor *dma_tx_desc; struct dma_async_tx_descriptor *dma_rx_desc; - dma_addr_t dma_tx_buf_bus; - dma_addr_t dma_rx_buf_bus; dma_cookie_t dma_tx_cookie; dma_cookie_t dma_rx_cookie; - unsigned char *dma_tx_buf_virt; - unsigned char *dma_rx_buf_virt; unsigned int dma_tx_bytes; unsigned int dma_rx_bytes; - int dma_tx_in_progress; - int dma_rx_in_progress; + bool dma_tx_in_progress; unsigned int dma_rx_timeout; struct timer_list lpuart_timer; + struct scatterlist rx_sgl, tx_sgl[2]; + struct circ_buf rx_ring; + int rx_dma_rng_buf_len; + unsigned int dma_tx_nents; + wait_queue_head_t dma_wait; }; static const struct of_device_id lpuart_dt_ids[] = { @@ -270,7 +274,6 @@ MODULE_DEVICE_TABLE(of, lpuart_dt_ids); /* Forward declare this for the dma callbacks*/ static void lpuart_dma_tx_complete(void *arg); -static void lpuart_dma_rx_complete(void *arg); static u32 lpuart32_read(void __iomem *addr) { @@ -316,141 +319,102 @@ static void lpuart32_stop_rx(struct uart_port *port) lpuart32_write(temp & ~UARTCTRL_RE, port->membase + UARTCTRL); } -static void lpuart_copy_rx_to_tty(struct lpuart_port *sport, - struct tty_port *tty, int count) +static void lpuart_dma_tx(struct lpuart_port *sport) { - int copied; - - sport->port.icount.rx += count; + struct circ_buf *xmit = &sport->port.state->xmit; + struct scatterlist *sgl = sport->tx_sgl; + struct device *dev = sport->port.dev; + int ret; - if (!tty) { - dev_err(sport->port.dev, "No tty port\n"); + if (sport->dma_tx_in_progress) return; - } - dma_sync_single_for_cpu(sport->port.dev, sport->dma_rx_buf_bus, - FSL_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE); - copied = tty_insert_flip_string(tty, - ((unsigned char *)(sport->dma_rx_buf_virt)), count); + sport->dma_tx_bytes = uart_circ_chars_pending(xmit); - if (copied != count) { - WARN_ON(1); - dev_err(sport->port.dev, "RxData copy to tty layer failed\n"); + if (xmit->tail < xmit->head || xmit->head == 0) { + sport->dma_tx_nents = 1; + sg_init_one(sgl, xmit->buf + xmit->tail, sport->dma_tx_bytes); + } else { + sport->dma_tx_nents = 2; + sg_init_table(sgl, 2); + sg_set_buf(sgl, xmit->buf + xmit->tail, + UART_XMIT_SIZE - xmit->tail); + sg_set_buf(sgl + 1, xmit->buf, xmit->head); } - dma_sync_single_for_device(sport->port.dev, sport->dma_rx_buf_bus, - FSL_UART_RX_DMA_BUFFER_SIZE, DMA_TO_DEVICE); -} - -static void lpuart_pio_tx(struct lpuart_port *sport) -{ - struct circ_buf *xmit = &sport->port.state->xmit; - unsigned long flags; - - spin_lock_irqsave(&sport->port.lock, flags); - - while (!uart_circ_empty(xmit) && - readb(sport->port.membase + UARTTCFIFO) < sport->txfifo_size) { - writeb(xmit->buf[xmit->tail], sport->port.membase + UARTDR); - xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); - sport->port.icount.tx++; + ret = dma_map_sg(dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE); + if (!ret) { + dev_err(dev, "DMA mapping error for TX.\n"); + return; } - if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) - uart_write_wakeup(&sport->port); - - if (uart_circ_empty(xmit)) - writeb(readb(sport->port.membase + UARTCR5) | UARTCR5_TDMAS, - sport->port.membase + UARTCR5); - - spin_unlock_irqrestore(&sport->port.lock, flags); -} - -static int lpuart_dma_tx(struct lpuart_port *sport, unsigned long count) -{ - struct circ_buf *xmit = &sport->port.state->xmit; - dma_addr_t tx_bus_addr; - - dma_sync_single_for_device(sport->port.dev, sport->dma_tx_buf_bus, - UART_XMIT_SIZE, DMA_TO_DEVICE); - sport->dma_tx_bytes = count & ~(sport->txfifo_size - 1); - tx_bus_addr = sport->dma_tx_buf_bus + xmit->tail; - sport->dma_tx_desc = dmaengine_prep_slave_single(sport->dma_tx_chan, - tx_bus_addr, sport->dma_tx_bytes, + sport->dma_tx_desc = dmaengine_prep_slave_sg(sport->dma_tx_chan, sgl, + sport->dma_tx_nents, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT); - if (!sport->dma_tx_desc) { - dev_err(sport->port.dev, "Not able to get desc for tx\n"); - return -EIO; + dma_unmap_sg(dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE); + dev_err(dev, "Cannot prepare TX slave DMA!\n"); + return; } sport->dma_tx_desc->callback = lpuart_dma_tx_complete; sport->dma_tx_desc->callback_param = sport; - sport->dma_tx_in_progress = 1; + sport->dma_tx_in_progress = true; sport->dma_tx_cookie = dmaengine_submit(sport->dma_tx_desc); dma_async_issue_pending(sport->dma_tx_chan); - - return 0; -} - -static void lpuart_prepare_tx(struct lpuart_port *sport) -{ - struct circ_buf *xmit = &sport->port.state->xmit; - unsigned long count = CIRC_CNT_TO_END(xmit->head, - xmit->tail, UART_XMIT_SIZE); - - if (!count) - return; - - if (count < sport->txfifo_size) - writeb(readb(sport->port.membase + UARTCR5) & ~UARTCR5_TDMAS, - sport->port.membase + UARTCR5); - else { - writeb(readb(sport->port.membase + UARTCR5) | UARTCR5_TDMAS, - sport->port.membase + UARTCR5); - lpuart_dma_tx(sport, count); - } } static void lpuart_dma_tx_complete(void *arg) { struct lpuart_port *sport = arg; + struct scatterlist *sgl = &sport->tx_sgl[0]; struct circ_buf *xmit = &sport->port.state->xmit; unsigned long flags; - async_tx_ack(sport->dma_tx_desc); - spin_lock_irqsave(&sport->port.lock, flags); + dma_unmap_sg(sport->port.dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE); + xmit->tail = (xmit->tail + sport->dma_tx_bytes) & (UART_XMIT_SIZE - 1); - sport->dma_tx_in_progress = 0; + + sport->port.icount.tx += sport->dma_tx_bytes; + sport->dma_tx_in_progress = false; + spin_unlock_irqrestore(&sport->port.lock, flags); if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(&sport->port); - lpuart_prepare_tx(sport); + if (waitqueue_active(&sport->dma_wait)) { + wake_up(&sport->dma_wait); + return; + } + + spin_lock_irqsave(&sport->port.lock, flags); + + if (!uart_circ_empty(xmit) && !uart_tx_stopped(&sport->port)) + lpuart_dma_tx(sport); spin_unlock_irqrestore(&sport->port.lock, flags); } -static int lpuart_dma_rx(struct lpuart_port *sport) +static int lpuart_dma_tx_request(struct uart_port *port) { - dma_sync_single_for_device(sport->port.dev, sport->dma_rx_buf_bus, - FSL_UART_RX_DMA_BUFFER_SIZE, DMA_TO_DEVICE); - sport->dma_rx_desc = dmaengine_prep_slave_single(sport->dma_rx_chan, - sport->dma_rx_buf_bus, FSL_UART_RX_DMA_BUFFER_SIZE, - DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); + struct lpuart_port *sport = container_of(port, + struct lpuart_port, port); + struct dma_slave_config dma_tx_sconfig = {}; + int ret; - if (!sport->dma_rx_desc) { - dev_err(sport->port.dev, "Not able to get desc for rx\n"); - return -EIO; - } + dma_tx_sconfig.dst_addr = sport->port.mapbase + UARTDR; + dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + dma_tx_sconfig.dst_maxburst = 1; + dma_tx_sconfig.direction = DMA_MEM_TO_DEV; + ret = dmaengine_slave_config(sport->dma_tx_chan, &dma_tx_sconfig); - sport->dma_rx_desc->callback = lpuart_dma_rx_complete; - sport->dma_rx_desc->callback_param = sport; - sport->dma_rx_in_progress = 1; - sport->dma_rx_cookie = dmaengine_submit(sport->dma_rx_desc); - dma_async_issue_pending(sport->dma_rx_chan); + if (ret) { + dev_err(sport->port.dev, + "DMA slave config failed, err = %d\n", ret); + return ret; + } return 0; } @@ -458,75 +422,76 @@ static int lpuart_dma_rx(struct lpuart_port *sport) static void lpuart_flush_buffer(struct uart_port *port) { struct lpuart_port *sport = container_of(port, struct lpuart_port, port); + if (sport->lpuart_dma_tx_use) { + if (sport->dma_tx_in_progress) { + dma_unmap_sg(sport->port.dev, &sport->tx_sgl[0], + sport->dma_tx_nents, DMA_TO_DEVICE); + sport->dma_tx_in_progress = false; + } dmaengine_terminate_all(sport->dma_tx_chan); - sport->dma_tx_in_progress = 0; } } -static void lpuart_dma_rx_complete(void *arg) +#if defined(CONFIG_CONSOLE_POLL) + +static int lpuart_poll_init(struct uart_port *port) { - struct lpuart_port *sport = arg; - struct tty_port *port = &sport->port.state->port; + struct lpuart_port *sport = container_of(port, + struct lpuart_port, port); unsigned long flags; + unsigned char temp; - async_tx_ack(sport->dma_rx_desc); - mod_timer(&sport->lpuart_timer, jiffies + sport->dma_rx_timeout); + sport->port.fifosize = 0; spin_lock_irqsave(&sport->port.lock, flags); + /* Disable Rx & Tx */ + writeb(0, sport->port.membase + UARTCR2); - sport->dma_rx_in_progress = 0; - lpuart_copy_rx_to_tty(sport, port, FSL_UART_RX_DMA_BUFFER_SIZE); - tty_flip_buffer_push(port); - lpuart_dma_rx(sport); - - spin_unlock_irqrestore(&sport->port.lock, flags); -} - -static void lpuart_timer_func(unsigned long data) -{ - struct lpuart_port *sport = (struct lpuart_port *)data; - struct tty_port *port = &sport->port.state->port; - struct dma_tx_state state; - unsigned long flags; - unsigned char temp; - int count; + temp = readb(sport->port.membase + UARTPFIFO); + /* Enable Rx and Tx FIFO */ + writeb(temp | UARTPFIFO_RXFE | UARTPFIFO_TXFE, + sport->port.membase + UARTPFIFO); - del_timer(&sport->lpuart_timer); - dmaengine_pause(sport->dma_rx_chan); - dmaengine_tx_status(sport->dma_rx_chan, sport->dma_rx_cookie, &state); - dmaengine_terminate_all(sport->dma_rx_chan); - count = FSL_UART_RX_DMA_BUFFER_SIZE - state.residue; - async_tx_ack(sport->dma_rx_desc); + /* flush Tx and Rx FIFO */ + writeb(UARTCFIFO_TXFLUSH | UARTCFIFO_RXFLUSH, + sport->port.membase + UARTCFIFO); - spin_lock_irqsave(&sport->port.lock, flags); + /* explicitly clear RDRF */ + if (readb(sport->port.membase + UARTSR1) & UARTSR1_RDRF) { + readb(sport->port.membase + UARTDR); + writeb(UARTSFIFO_RXUF, sport->port.membase + UARTSFIFO); + } - sport->dma_rx_in_progress = 0; - lpuart_copy_rx_to_tty(sport, port, count); - tty_flip_buffer_push(port); - temp = readb(sport->port.membase + UARTCR5); - writeb(temp & ~UARTCR5_RDMAS, sport->port.membase + UARTCR5); + writeb(0, sport->port.membase + UARTTWFIFO); + writeb(1, sport->port.membase + UARTRWFIFO); + /* Enable Rx and Tx */ + writeb(UARTCR2_RE | UARTCR2_TE, sport->port.membase + UARTCR2); spin_unlock_irqrestore(&sport->port.lock, flags); + + return 0; } -static inline void lpuart_prepare_rx(struct lpuart_port *sport) +static void lpuart_poll_put_char(struct uart_port *port, unsigned char c) { - unsigned long flags; - unsigned char temp; - - spin_lock_irqsave(&sport->port.lock, flags); + /* drain */ + while (!(readb(port->membase + UARTSR1) & UARTSR1_TDRE)) + barrier(); - sport->lpuart_timer.expires = jiffies + sport->dma_rx_timeout; - add_timer(&sport->lpuart_timer); + writeb(c, port->membase + UARTDR); +} - lpuart_dma_rx(sport); - temp = readb(sport->port.membase + UARTCR5); - writeb(temp | UARTCR5_RDMAS, sport->port.membase + UARTCR5); +static int lpuart_poll_get_char(struct uart_port *port) +{ + if (!(readb(port->membase + UARTSR1) & UARTSR1_RDRF)) + return NO_POLL_CHAR; - spin_unlock_irqrestore(&sport->port.lock, flags); + return readb(port->membase + UARTDR); } +#endif + static inline void lpuart_transmit_buffer(struct lpuart_port *sport) { struct circ_buf *xmit = &sport->port.state->xmit; @@ -580,8 +545,8 @@ static void lpuart_start_tx(struct uart_port *port) writeb(temp | UARTCR2_TIE, port->membase + UARTCR2); if (sport->lpuart_dma_tx_use) { - if (!uart_circ_empty(xmit) && !sport->dma_tx_in_progress) - lpuart_prepare_tx(sport); + if (!uart_circ_empty(xmit) && !uart_tx_stopped(port)) + lpuart_dma_tx(sport); } else { if (readb(port->membase + UARTSR1) & UARTSR1_TDRE) lpuart_transmit_buffer(sport); @@ -600,6 +565,29 @@ static void lpuart32_start_tx(struct uart_port *port) lpuart32_transmit_buffer(sport); } +/* return TIOCSER_TEMT when transmitter is not busy */ +static unsigned int lpuart_tx_empty(struct uart_port *port) +{ + struct lpuart_port *sport = container_of(port, + struct lpuart_port, port); + unsigned char sr1 = readb(port->membase + UARTSR1); + unsigned char sfifo = readb(port->membase + UARTSFIFO); + + if (sport->dma_tx_in_progress) + return 0; + + if (sr1 & UARTSR1_TC && sfifo & UARTSFIFO_TXEMPT) + return TIOCSER_TEMT; + + return 0; +} + +static unsigned int lpuart32_tx_empty(struct uart_port *port) +{ + return (lpuart32_read(port->membase + UARTSTAT) & UARTSTAT_TC) ? + TIOCSER_TEMT : 0; +} + static irqreturn_t lpuart_txint(int irq, void *dev_id) { struct lpuart_port *sport = dev_id; @@ -639,11 +627,14 @@ out: static irqreturn_t lpuart_rxint(int irq, void *dev_id) { struct lpuart_port *sport = dev_id; - unsigned int flg, ignored = 0; + unsigned int flg, ignored = 0, overrun = 0; struct tty_port *port = &sport->port.state->port; unsigned long flags; unsigned char rx, sr; + if (sport->port.irq_wake) + pm_wakeup_event(port->tty->dev, 0); + spin_lock_irqsave(&sport->port.lock, flags); while (!(readb(sport->port.membase + UARTSFIFO) & UARTSFIFO_RXEMPT)) { @@ -666,7 +657,7 @@ static irqreturn_t lpuart_rxint(int irq, void *dev_id) sport->port.icount.frame++; if (sr & UARTSR1_OR) - sport->port.icount.overrun++; + overrun++; if (sr & sport->port.ignore_status_mask) { if (++ignored > 100) @@ -693,6 +684,17 @@ static irqreturn_t lpuart_rxint(int irq, void *dev_id) } out: + if (overrun) { + sport->port.icount.overrun += overrun; + + /* + * Overruns cause FIFO pointers to become missaligned. + * Flushing the receive FIFO reinitializes the pointers. + */ + writeb(UARTCFIFO_RXFLUSH, sport->port.membase + UARTCFIFO); + writeb(UARTSFIFO_RXOF, sport->port.membase + UARTSFIFO); + } + spin_unlock_irqrestore(&sport->port.lock, flags); tty_flip_buffer_push(port); @@ -766,23 +768,15 @@ out: static irqreturn_t lpuart_int(int irq, void *dev_id) { struct lpuart_port *sport = dev_id; - unsigned char sts, crdma; + unsigned char sts; sts = readb(sport->port.membase + UARTSR1); - crdma = readb(sport->port.membase + UARTCR5); - if (sts & UARTSR1_RDRF && !(crdma & UARTCR5_RDMAS)) { - if (sport->lpuart_dma_rx_use) - lpuart_prepare_rx(sport); - else - lpuart_rxint(irq, dev_id); - } - if (sts & UARTSR1_TDRE && !(crdma & UARTCR5_TDMAS)) { - if (sport->lpuart_dma_tx_use) - lpuart_pio_tx(sport); - else - lpuart_txint(irq, dev_id); - } + if (sts & UARTSR1_RDRF) + lpuart_rxint(irq, dev_id); + + if (sts & UARTSR1_TDRE) + lpuart_txint(irq, dev_id); return IRQ_HANDLED; } @@ -807,17 +801,264 @@ static irqreturn_t lpuart32_int(int irq, void *dev_id) return IRQ_HANDLED; } -/* return TIOCSER_TEMT when transmitter is not busy */ -static unsigned int lpuart_tx_empty(struct uart_port *port) +static void lpuart_copy_rx_to_tty(struct lpuart_port *sport) { - return (readb(port->membase + UARTSR1) & UARTSR1_TC) ? - TIOCSER_TEMT : 0; + struct tty_port *port = &sport->port.state->port; + struct dma_tx_state state; + enum dma_status dmastat; + struct circ_buf *ring = &sport->rx_ring; + unsigned long flags; + int count = 0; + unsigned char sr; + + sr = readb(sport->port.membase + UARTSR1); + + if (sr & (UARTSR1_PE | UARTSR1_FE)) { + unsigned char cr2; + + /* Disable receiver during this operation... */ + cr2 = readb(sport->port.membase + UARTCR2); + cr2 &= ~(UARTCR2_RE); + writeb(cr2, sport->port.membase + UARTCR2); + + /* Read DR to clear the error flags */ + readb(sport->port.membase + UARTDR); + + if (sr & UARTSR1_PE) + sport->port.icount.parity++; + else if (sr & UARTSR1_FE) + sport->port.icount.frame++; + + /* + * At this point parity/framing error is cleared + * However, since the DMA already read the data register + * and we had to read it again after reading the status + * register to properly clear the flags, the FIFO actually + * underflowed... This requires a clearing of the FIFO... + */ + if (readb(sport->port.membase + UARTSFIFO) & UARTSFIFO_RXUF) { + writeb(UARTSFIFO_RXUF, sport->port.membase + UARTSFIFO); + writeb(UARTCFIFO_RXFLUSH, sport->port.membase + UARTCFIFO); + } + + cr2 |= UARTCR2_RE; + writeb(cr2, sport->port.membase + UARTCR2); + } + + async_tx_ack(sport->dma_rx_desc); + + spin_lock_irqsave(&sport->port.lock, flags); + + dmastat = dmaengine_tx_status(sport->dma_rx_chan, + sport->dma_rx_cookie, + &state); + + if (dmastat == DMA_ERROR) { + dev_err(sport->port.dev, "Rx DMA transfer failed!\n"); + spin_unlock_irqrestore(&sport->port.lock, flags); + return; + } + + /* CPU claims ownership of RX DMA buffer */ + dma_sync_sg_for_cpu(sport->port.dev, &sport->rx_sgl, 1, DMA_FROM_DEVICE); + + /* + * ring->head points to the end of data already written by the DMA. + * ring->tail points to the beginning of data to be read by the + * framework. + * The current transfer size should not be larger than the dma buffer + * length. + */ + ring->head = sport->rx_sgl.length - state.residue; + BUG_ON(ring->head > sport->rx_sgl.length); + /* + * At this point ring->head may point to the first byte right after the + * last byte of the dma buffer: + * 0 <= ring->head <= sport->rx_sgl.length + * + * However ring->tail must always points inside the dma buffer: + * 0 <= ring->tail <= sport->rx_sgl.length - 1 + * + * Since we use a ring buffer, we have to handle the case + * where head is lower than tail. In such a case, we first read from + * tail to the end of the buffer then reset tail. + */ + if (ring->head < ring->tail) { + count = sport->rx_sgl.length - ring->tail; + + tty_insert_flip_string(port, ring->buf + ring->tail, count); + ring->tail = 0; + sport->port.icount.rx += count; + } + + /* Finally we read data from tail to head */ + if (ring->tail < ring->head) { + count = ring->head - ring->tail; + tty_insert_flip_string(port, ring->buf + ring->tail, count); + /* Wrap ring->head if needed */ + if (ring->head >= sport->rx_sgl.length) + ring->head = 0; + ring->tail = ring->head; + sport->port.icount.rx += count; + } + + dma_sync_sg_for_device(sport->port.dev, &sport->rx_sgl, 1, + DMA_FROM_DEVICE); + + spin_unlock_irqrestore(&sport->port.lock, flags); + + tty_flip_buffer_push(port); + mod_timer(&sport->lpuart_timer, jiffies + sport->dma_rx_timeout); } -static unsigned int lpuart32_tx_empty(struct uart_port *port) +static void lpuart_dma_rx_complete(void *arg) { - return (lpuart32_read(port->membase + UARTSTAT) & UARTSTAT_TC) ? - TIOCSER_TEMT : 0; + struct lpuart_port *sport = arg; + + lpuart_copy_rx_to_tty(sport); +} + +static void lpuart_timer_func(unsigned long data) +{ + struct lpuart_port *sport = (struct lpuart_port *)data; + + lpuart_copy_rx_to_tty(sport); +} + +static inline int lpuart_start_rx_dma(struct lpuart_port *sport) +{ + struct dma_slave_config dma_rx_sconfig = {}; + struct circ_buf *ring = &sport->rx_ring; + int ret, nent; + int bits, baud; + struct tty_port *port = &sport->port.state->port; + struct tty_struct *tty = port->tty; + struct ktermios *termios = &tty->termios; + + baud = tty_get_baud_rate(tty); + + bits = (termios->c_cflag & CSIZE) == CS7 ? 9 : 10; + if (termios->c_cflag & PARENB) + bits++; + + /* + * Calculate length of one DMA buffer size to keep latency below + * 10ms at any baud rate. + */ + sport->rx_dma_rng_buf_len = (DMA_RX_TIMEOUT * baud / bits / 1000) * 2; + sport->rx_dma_rng_buf_len = (1 << (fls(sport->rx_dma_rng_buf_len) - 1)); + if (sport->rx_dma_rng_buf_len < 16) + sport->rx_dma_rng_buf_len = 16; + + ring->buf = kmalloc(sport->rx_dma_rng_buf_len, GFP_ATOMIC); + if (!ring->buf) { + dev_err(sport->port.dev, "Ring buf alloc failed\n"); + return -ENOMEM; + } + + sg_init_one(&sport->rx_sgl, ring->buf, sport->rx_dma_rng_buf_len); + sg_set_buf(&sport->rx_sgl, ring->buf, sport->rx_dma_rng_buf_len); + nent = dma_map_sg(sport->port.dev, &sport->rx_sgl, 1, DMA_FROM_DEVICE); + + if (!nent) { + dev_err(sport->port.dev, "DMA Rx mapping error\n"); + return -EINVAL; + } + + dma_rx_sconfig.src_addr = sport->port.mapbase + UARTDR; + dma_rx_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + dma_rx_sconfig.src_maxburst = 1; + dma_rx_sconfig.direction = DMA_DEV_TO_MEM; + ret = dmaengine_slave_config(sport->dma_rx_chan, &dma_rx_sconfig); + + if (ret < 0) { + dev_err(sport->port.dev, + "DMA Rx slave config failed, err = %d\n", ret); + return ret; + } + + sport->dma_rx_desc = dmaengine_prep_dma_cyclic(sport->dma_rx_chan, + sg_dma_address(&sport->rx_sgl), + sport->rx_sgl.length, + sport->rx_sgl.length / 2, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!sport->dma_rx_desc) { + dev_err(sport->port.dev, "Cannot prepare cyclic DMA\n"); + return -EFAULT; + } + + sport->dma_rx_desc->callback = lpuart_dma_rx_complete; + sport->dma_rx_desc->callback_param = sport; + sport->dma_rx_cookie = dmaengine_submit(sport->dma_rx_desc); + dma_async_issue_pending(sport->dma_rx_chan); + + writeb(readb(sport->port.membase + UARTCR5) | UARTCR5_RDMAS, + sport->port.membase + UARTCR5); + + return 0; +} + +static void lpuart_dma_rx_free(struct uart_port *port) +{ + struct lpuart_port *sport = container_of(port, + struct lpuart_port, port); + + if (sport->dma_rx_chan) + dmaengine_terminate_all(sport->dma_rx_chan); + + dma_unmap_sg(sport->port.dev, &sport->rx_sgl, 1, DMA_FROM_DEVICE); + kfree(sport->rx_ring.buf); + sport->rx_ring.tail = 0; + sport->rx_ring.head = 0; + sport->dma_rx_desc = NULL; + sport->dma_rx_cookie = -EINVAL; +} + +static int lpuart_config_rs485(struct uart_port *port, + struct serial_rs485 *rs485) +{ + struct lpuart_port *sport = container_of(port, + struct lpuart_port, port); + + u8 modem = readb(sport->port.membase + UARTMODEM) & + ~(UARTMODEM_TXRTSPOL | UARTMODEM_TXRTSE); + writeb(modem, sport->port.membase + UARTMODEM); + + if (rs485->flags & SER_RS485_ENABLED) { + /* Enable auto RS-485 RTS mode */ + modem |= UARTMODEM_TXRTSE; + + /* + * RTS needs to be logic HIGH either during transer _or_ after + * transfer, other variants are not supported by the hardware. + */ + + if (!(rs485->flags & (SER_RS485_RTS_ON_SEND | + SER_RS485_RTS_AFTER_SEND))) + rs485->flags |= SER_RS485_RTS_ON_SEND; + + if (rs485->flags & SER_RS485_RTS_ON_SEND && + rs485->flags & SER_RS485_RTS_AFTER_SEND) + rs485->flags &= ~SER_RS485_RTS_AFTER_SEND; + + /* + * The hardware defaults to RTS logic HIGH while transfer. + * Switch polarity in case RTS shall be logic HIGH + * after transfer. + * Note: UART is assumed to be active high. + */ + if (rs485->flags & SER_RS485_RTS_ON_SEND) + modem &= ~UARTMODEM_TXRTSPOL; + else if (rs485->flags & SER_RS485_RTS_AFTER_SEND) + modem |= UARTMODEM_TXRTSPOL; + } + + /* Store the new configuration */ + sport->port.rs485 = *rs485; + + writeb(modem, sport->port.membase + UARTMODEM); + return 0; } static unsigned int lpuart_get_mctrl(struct uart_port *port) @@ -853,17 +1094,22 @@ static unsigned int lpuart32_get_mctrl(struct uart_port *port) static void lpuart_set_mctrl(struct uart_port *port, unsigned int mctrl) { unsigned char temp; + struct lpuart_port *sport = container_of(port, + struct lpuart_port, port); - temp = readb(port->membase + UARTMODEM) & + /* Make sure RXRTSE bit is not set when RS485 is enabled */ + if (!(sport->port.rs485.flags & SER_RS485_ENABLED)) { + temp = readb(sport->port.membase + UARTMODEM) & ~(UARTMODEM_RXRTSE | UARTMODEM_TXCTSE); - if (mctrl & TIOCM_RTS) - temp |= UARTMODEM_RXRTSE; + if (mctrl & TIOCM_RTS) + temp |= UARTMODEM_RXRTSE; - if (mctrl & TIOCM_CTS) - temp |= UARTMODEM_TXCTSE; + if (mctrl & TIOCM_CTS) + temp |= UARTMODEM_TXCTSE; - writeb(temp, port->membase + UARTMODEM); + writeb(temp, port->membase + UARTMODEM); + } } static void lpuart32_set_mctrl(struct uart_port *port, unsigned int mctrl) @@ -921,13 +1167,16 @@ static void lpuart_setup_watermark(struct lpuart_port *sport) writeb(val | UARTPFIFO_TXFE | UARTPFIFO_RXFE, sport->port.membase + UARTPFIFO); - /* explicitly clear RDRF */ - readb(sport->port.membase + UARTSR1); - /* flush Tx and Rx FIFO */ writeb(UARTCFIFO_TXFLUSH | UARTCFIFO_RXFLUSH, sport->port.membase + UARTCFIFO); + /* explicitly clear RDRF */ + if (readb(sport->port.membase + UARTSR1) & UARTSR1_RDRF) { + readb(sport->port.membase + UARTDR); + writeb(UARTSFIFO_RXUF, sport->port.membase + UARTSFIFO); + } + writeb(0, sport->port.membase + UARTTWFIFO); writeb(1, sport->port.membase + UARTRWFIFO); @@ -960,116 +1209,17 @@ static void lpuart32_setup_watermark(struct lpuart_port *sport) lpuart32_write(ctrl_saved, sport->port.membase + UARTCTRL); } -static int lpuart_dma_tx_request(struct uart_port *port) +static void rx_dma_timer_init(struct lpuart_port *sport) { - struct lpuart_port *sport = container_of(port, - struct lpuart_port, port); - struct dma_slave_config dma_tx_sconfig; - dma_addr_t dma_bus; - unsigned char *dma_buf; - int ret; - - dma_bus = dma_map_single(sport->dma_tx_chan->device->dev, - sport->port.state->xmit.buf, - UART_XMIT_SIZE, DMA_TO_DEVICE); - - if (dma_mapping_error(sport->dma_tx_chan->device->dev, dma_bus)) { - dev_err(sport->port.dev, "dma_map_single tx failed\n"); - return -ENOMEM; - } - - dma_buf = sport->port.state->xmit.buf; - dma_tx_sconfig.dst_addr = sport->port.mapbase + UARTDR; - dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; - dma_tx_sconfig.dst_maxburst = sport->txfifo_size; - dma_tx_sconfig.direction = DMA_MEM_TO_DEV; - ret = dmaengine_slave_config(sport->dma_tx_chan, &dma_tx_sconfig); - - if (ret < 0) { - dev_err(sport->port.dev, - "Dma slave config failed, err = %d\n", ret); - return ret; - } - - sport->dma_tx_buf_virt = dma_buf; - sport->dma_tx_buf_bus = dma_bus; - sport->dma_tx_in_progress = 0; - - return 0; -} - -static int lpuart_dma_rx_request(struct uart_port *port) -{ - struct lpuart_port *sport = container_of(port, - struct lpuart_port, port); - struct dma_slave_config dma_rx_sconfig; - dma_addr_t dma_bus; - unsigned char *dma_buf; - int ret; - - dma_buf = devm_kzalloc(sport->port.dev, - FSL_UART_RX_DMA_BUFFER_SIZE, GFP_KERNEL); - - if (!dma_buf) { - dev_err(sport->port.dev, "Dma rx alloc failed\n"); - return -ENOMEM; - } - - dma_bus = dma_map_single(sport->dma_rx_chan->device->dev, dma_buf, - FSL_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE); - - if (dma_mapping_error(sport->dma_rx_chan->device->dev, dma_bus)) { - dev_err(sport->port.dev, "dma_map_single rx failed\n"); - return -ENOMEM; - } - - dma_rx_sconfig.src_addr = sport->port.mapbase + UARTDR; - dma_rx_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; - dma_rx_sconfig.src_maxburst = 1; - dma_rx_sconfig.direction = DMA_DEV_TO_MEM; - ret = dmaengine_slave_config(sport->dma_rx_chan, &dma_rx_sconfig); - - if (ret < 0) { - dev_err(sport->port.dev, - "Dma slave config failed, err = %d\n", ret); - return ret; - } - - sport->dma_rx_buf_virt = dma_buf; - sport->dma_rx_buf_bus = dma_bus; - sport->dma_rx_in_progress = 0; - - return 0; -} - -static void lpuart_dma_tx_free(struct uart_port *port) -{ - struct lpuart_port *sport = container_of(port, - struct lpuart_port, port); - - dma_unmap_single(sport->port.dev, sport->dma_tx_buf_bus, - UART_XMIT_SIZE, DMA_TO_DEVICE); - - sport->dma_tx_buf_bus = 0; - sport->dma_tx_buf_virt = NULL; -} - -static void lpuart_dma_rx_free(struct uart_port *port) -{ - struct lpuart_port *sport = container_of(port, - struct lpuart_port, port); - - dma_unmap_single(sport->port.dev, sport->dma_rx_buf_bus, - FSL_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE); - - sport->dma_rx_buf_bus = 0; - sport->dma_rx_buf_virt = NULL; + setup_timer(&sport->lpuart_timer, lpuart_timer_func, + (unsigned long)sport); + sport->lpuart_timer.expires = jiffies + sport->dma_rx_timeout; + add_timer(&sport->lpuart_timer); } static int lpuart_startup(struct uart_port *port) { struct lpuart_port *sport = container_of(port, struct lpuart_port, port); - int ret; unsigned long flags; unsigned char temp; @@ -1084,43 +1234,43 @@ static int lpuart_startup(struct uart_port *port) sport->rxfifo_size = 0x1 << (((temp >> UARTPFIFO_RXSIZE_OFF) & UARTPFIFO_FIFOSIZE_MASK) + 1); - if (sport->dma_rx_chan && !lpuart_dma_rx_request(port)) { + spin_lock_irqsave(&sport->port.lock, flags); + + lpuart_setup_watermark(sport); + + temp = readb(sport->port.membase + UARTCR2); + temp |= (UARTCR2_RIE | UARTCR2_TIE | UARTCR2_RE | UARTCR2_TE); + writeb(temp, sport->port.membase + UARTCR2); + + if (sport->dma_rx_chan && !lpuart_start_rx_dma(sport)) { + /* set Rx DMA timeout */ + sport->dma_rx_timeout = msecs_to_jiffies(DMA_RX_TIMEOUT); + if (!sport->dma_rx_timeout) + sport->dma_rx_timeout = 1; + sport->lpuart_dma_rx_use = true; - setup_timer(&sport->lpuart_timer, lpuart_timer_func, - (unsigned long)sport); - } else + rx_dma_timer_init(sport); + } else { sport->lpuart_dma_rx_use = false; - + } if (sport->dma_tx_chan && !lpuart_dma_tx_request(port)) { + init_waitqueue_head(&sport->dma_wait); sport->lpuart_dma_tx_use = true; temp = readb(port->membase + UARTCR5); - temp &= ~UARTCR5_RDMAS; writeb(temp | UARTCR5_TDMAS, port->membase + UARTCR5); - } else + } else { sport->lpuart_dma_tx_use = false; - - ret = devm_request_irq(port->dev, port->irq, lpuart_int, 0, - DRIVER_NAME, sport); - if (ret) - return ret; - - spin_lock_irqsave(&sport->port.lock, flags); - - lpuart_setup_watermark(sport); - - temp = readb(sport->port.membase + UARTCR2); - temp |= (UARTCR2_RIE | UARTCR2_TIE | UARTCR2_RE | UARTCR2_TE); - writeb(temp, sport->port.membase + UARTCR2); + } spin_unlock_irqrestore(&sport->port.lock, flags); + return 0; } static int lpuart32_startup(struct uart_port *port) { struct lpuart_port *sport = container_of(port, struct lpuart_port, port); - int ret; unsigned long flags; unsigned long temp; @@ -1133,11 +1283,6 @@ static int lpuart32_startup(struct uart_port *port) sport->rxfifo_size = 0x1 << (((temp >> UARTFIFO_RXSIZE_OFF) & UARTFIFO_FIFOSIZE_MASK) - 1); - ret = devm_request_irq(port->dev, port->irq, lpuart32_int, 0, - DRIVER_NAME, sport); - if (ret) - return ret; - spin_lock_irqsave(&sport->port.lock, flags); lpuart32_setup_watermark(sport); @@ -1167,20 +1312,24 @@ static void lpuart_shutdown(struct uart_port *port) spin_unlock_irqrestore(&port->lock, flags); - devm_free_irq(port->dev, port->irq, sport); - if (sport->lpuart_dma_rx_use) { - lpuart_dma_rx_free(&sport->port); del_timer_sync(&sport->lpuart_timer); + lpuart_dma_rx_free(&sport->port); } - if (sport->lpuart_dma_tx_use) - lpuart_dma_tx_free(&sport->port); + if (sport->lpuart_dma_tx_use) { + if (wait_event_interruptible(sport->dma_wait, + !sport->dma_tx_in_progress) != false) { + sport->dma_tx_in_progress = false; + dmaengine_terminate_all(sport->dma_tx_chan); + } + + lpuart_stop_tx(port); + } } static void lpuart32_shutdown(struct uart_port *port) { - struct lpuart_port *sport = container_of(port, struct lpuart_port, port); unsigned long temp; unsigned long flags; @@ -1193,8 +1342,6 @@ static void lpuart32_shutdown(struct uart_port *port) lpuart32_write(temp, port->membase + UARTCTRL); spin_unlock_irqrestore(&port->lock, flags); - - devm_free_irq(port->dev, port->irq, sport); } static void @@ -1203,13 +1350,14 @@ lpuart_set_termios(struct uart_port *port, struct ktermios *termios, { struct lpuart_port *sport = container_of(port, struct lpuart_port, port); unsigned long flags; - unsigned char cr1, old_cr1, old_cr2, cr4, bdh, modem; + unsigned char cr1, old_cr1, old_cr2, cr3, cr4, bdh, modem; unsigned int baud; unsigned int old_csize = old ? old->c_cflag & CSIZE : CS8; unsigned int sbr, brfa; cr1 = old_cr1 = readb(sport->port.membase + UARTCR1); old_cr2 = readb(sport->port.membase + UARTCR2); + cr3 = readb(sport->port.membase + UARTCR3); cr4 = readb(sport->port.membase + UARTCR4); bdh = readb(sport->port.membase + UARTBDH); modem = readb(sport->port.membase + UARTMODEM); @@ -1240,6 +1388,13 @@ lpuart_set_termios(struct uart_port *port, struct ktermios *termios, cr1 |= UARTCR1_M; } + /* + * When auto RS-485 RTS mode is enabled, + * hardware flow control need to be disabled. + */ + if (sport->port.rs485.flags & SER_RS485_ENABLED) + termios->c_cflag &= ~CRTSCTS; + if (termios->c_cflag & CRTSCTS) { modem |= (UARTMODEM_RXRTSE | UARTMODEM_TXCTSE); } else { @@ -1257,7 +1412,10 @@ lpuart_set_termios(struct uart_port *port, struct ktermios *termios, if ((termios->c_cflag & PARENB)) { if (termios->c_cflag & CMSPAR) { cr1 &= ~UARTCR1_PE; - cr1 |= UARTCR1_M; + if (termios->c_cflag & PARODD) + cr3 |= UARTCR3_T8; + else + cr3 &= ~UARTCR3_T8; } else { cr1 |= UARTCR1_PE; if ((termios->c_cflag & CSIZE) == CS8) @@ -1274,6 +1432,18 @@ lpuart_set_termios(struct uart_port *port, struct ktermios *termios, /* ask the core to calculate the divisor */ baud = uart_get_baud_rate(port, termios, old, 50, port->uartclk / 16); + /* + * Need to update the Ring buffer length according to the selected + * baud rate and restart Rx DMA path. + * + * Since timer function acqures sport->port.lock, need to stop before + * acquring same lock because otherwise del_timer_sync() can deadlock. + */ + if (old && sport->lpuart_dma_rx_use) { + del_timer_sync(&sport->lpuart_timer); + lpuart_dma_rx_free(&sport->port); + } + spin_lock_irqsave(&sport->port.lock, flags); sport->port.read_status_mask = 0; @@ -1299,17 +1469,6 @@ lpuart_set_termios(struct uart_port *port, struct ktermios *termios, /* update the per-port timeout */ uart_update_timeout(port, termios->c_cflag, baud); - if (sport->lpuart_dma_rx_use) { - /* Calculate delay for 1.5 DMA buffers */ - sport->dma_rx_timeout = (sport->port.timeout - HZ / 50) * - FSL_UART_RX_DMA_BUFFER_SIZE * 3 / - sport->rxfifo_size / 2; - dev_dbg(port->dev, "DMA Rx t-out %ums, tty t-out %u jiffies\n", - sport->dma_rx_timeout * 1000 / HZ, sport->port.timeout); - if (sport->dma_rx_timeout < msecs_to_jiffies(20)) - sport->dma_rx_timeout = msecs_to_jiffies(20); - } - /* wait transmit engin complete */ while (!(readb(sport->port.membase + UARTSR1) & UARTSR1_TC)) barrier(); @@ -1327,12 +1486,20 @@ lpuart_set_termios(struct uart_port *port, struct ktermios *termios, writeb(cr4 | brfa, sport->port.membase + UARTCR4); writeb(bdh, sport->port.membase + UARTBDH); writeb(sbr & 0xFF, sport->port.membase + UARTBDL); + writeb(cr3, sport->port.membase + UARTCR3); writeb(cr1, sport->port.membase + UARTCR1); writeb(modem, sport->port.membase + UARTMODEM); /* restore control register */ writeb(old_cr2, sport->port.membase + UARTCR2); + if (old && sport->lpuart_dma_rx_use) { + if (!lpuart_start_rx_dma(sport)) + rx_dma_timer_init(sport); + else + sport->lpuart_dma_rx_use = false; + } + spin_unlock_irqrestore(&sport->port.lock, flags); } @@ -1498,7 +1665,7 @@ static int lpuart_verify_port(struct uart_port *port, struct serial_struct *ser) return ret; } -static struct uart_ops lpuart_pops = { +static const struct uart_ops lpuart_pops = { .tx_empty = lpuart_tx_empty, .set_mctrl = lpuart_set_mctrl, .get_mctrl = lpuart_get_mctrl, @@ -1515,9 +1682,14 @@ static struct uart_ops lpuart_pops = { .config_port = lpuart_config_port, .verify_port = lpuart_verify_port, .flush_buffer = lpuart_flush_buffer, +#if defined(CONFIG_CONSOLE_POLL) + .poll_init = lpuart_poll_init, + .poll_get_char = lpuart_poll_get_char, + .poll_put_char = lpuart_poll_put_char, +#endif }; -static struct uart_ops lpuart32_pops = { +static const struct uart_ops lpuart32_pops = { .tx_empty = lpuart32_tx_empty, .set_mctrl = lpuart32_set_mctrl, .get_mctrl = lpuart32_get_mctrl, @@ -1560,6 +1732,13 @@ lpuart_console_write(struct console *co, const char *s, unsigned int count) { struct lpuart_port *sport = lpuart_ports[co->index]; unsigned char old_cr2, cr2; + unsigned long flags; + int locked = 1; + + if (sport->port.sysrq || oops_in_progress) + locked = spin_trylock_irqsave(&sport->port.lock, flags); + else + spin_lock_irqsave(&sport->port.lock, flags); /* first save CR2 and then disable interrupts */ cr2 = old_cr2 = readb(sport->port.membase + UARTCR2); @@ -1574,6 +1753,9 @@ lpuart_console_write(struct console *co, const char *s, unsigned int count) barrier(); writeb(old_cr2, sport->port.membase + UARTCR2); + + if (locked) + spin_unlock_irqrestore(&sport->port.lock, flags); } static void @@ -1581,6 +1763,13 @@ lpuart32_console_write(struct console *co, const char *s, unsigned int count) { struct lpuart_port *sport = lpuart_ports[co->index]; unsigned long old_cr, cr; + unsigned long flags; + int locked = 1; + + if (sport->port.sysrq || oops_in_progress) + locked = spin_trylock_irqsave(&sport->port.lock, flags); + else + spin_lock_irqsave(&sport->port.lock, flags); /* first save CR2 and then disable interrupts */ cr = old_cr = lpuart32_read(sport->port.membase + UARTCTRL); @@ -1595,6 +1784,9 @@ lpuart32_console_write(struct console *co, const char *s, unsigned int count) barrier(); lpuart32_write(old_cr, sport->port.membase + UARTCTRL); + + if (locked) + spin_unlock_irqrestore(&sport->port.lock, flags); } /* @@ -1804,6 +1996,18 @@ static struct uart_driver lpuart_reg = { .cons = LPUART_CONSOLE, }; +static struct dma_chan *lpuart_request_dma_chan(struct lpuart_port *sport, + const char *name) +{ + struct dma_chan *chan; + + chan = dma_request_slave_channel(sport->port.dev, name); + if (!chan) + dev_info(sport->port.dev, "DMA %s channel request failed, " + "operating without %s DMA\n", name, name); + return chan; +} + static int lpuart_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; @@ -1838,13 +2042,21 @@ static int lpuart_probe(struct platform_device *pdev) sport->port.dev = &pdev->dev; sport->port.type = PORT_LPUART; sport->port.iotype = UPIO_MEM; - sport->port.irq = platform_get_irq(pdev, 0); + ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(&pdev->dev, "cannot obtain irq\n"); + return ret; + } + sport->port.irq = ret; + if (sport->lpuart32) sport->port.ops = &lpuart32_pops; else sport->port.ops = &lpuart_pops; sport->port.flags = UPF_BOOT_AUTOCONF; + sport->port.rs485_config = lpuart_config_rs485; + sport->clk = devm_clk_get(&pdev->dev, "ipg"); if (IS_ERR(sport->clk)) { ret = PTR_ERR(sport->clk); @@ -1864,28 +2076,40 @@ static int lpuart_probe(struct platform_device *pdev) platform_set_drvdata(pdev, &sport->port); - if (sport->lpuart32) + if (sport->lpuart32) { lpuart_reg.cons = LPUART32_CONSOLE; - else + ret = devm_request_irq(&pdev->dev, sport->port.irq, lpuart32_int, 0, + DRIVER_NAME, sport); + } else { lpuart_reg.cons = LPUART_CONSOLE; + ret = devm_request_irq(&pdev->dev, sport->port.irq, lpuart_int, 0, + DRIVER_NAME, sport); + } + + if (ret) + goto failed_irq_request; ret = uart_add_one_port(&lpuart_reg, &sport->port); - if (ret) { - clk_disable_unprepare(sport->clk); - return ret; - } + if (ret) + goto failed_attach_port; - sport->dma_tx_chan = dma_request_slave_channel(sport->port.dev, "tx"); - if (!sport->dma_tx_chan) - dev_info(sport->port.dev, "DMA tx channel request failed, " - "operating without tx DMA\n"); + if (!nodma) { + sport->dma_tx_chan = lpuart_request_dma_chan(sport, "tx"); + sport->dma_rx_chan = lpuart_request_dma_chan(sport, "rx"); + } - sport->dma_rx_chan = dma_request_slave_channel(sport->port.dev, "rx"); - if (!sport->dma_rx_chan) - dev_info(sport->port.dev, "DMA rx channel request failed, " - "operating without rx DMA\n"); + if (of_property_read_bool(np, "linux,rs485-enabled-at-boot-time")) { + sport->port.rs485.flags |= SER_RS485_ENABLED; + sport->port.rs485.flags |= SER_RS485_RTS_ON_SEND; + writeb(UARTMODEM_TXRTSE, sport->port.membase + UARTMODEM); + } return 0; + +failed_attach_port: +failed_irq_request: + clk_disable_unprepare(sport->clk); + return ret; } static int lpuart_remove(struct platform_device *pdev) @@ -1925,6 +2149,32 @@ static int lpuart_suspend(struct device *dev) uart_suspend_port(&lpuart_reg, &sport->port); + if (sport->lpuart_dma_rx_use) { + /* + * EDMA driver during suspend will forcefully release any + * non-idle DMA channels. If port wakeup is enabled or if port + * is console port or 'no_console_suspend' is set the Rx DMA + * cannot resume as as expected, hence gracefully release the + * Rx DMA path before suspend and start Rx DMA path on resume. + */ + if (sport->port.irq_wake) { + del_timer_sync(&sport->lpuart_timer); + lpuart_dma_rx_free(&sport->port); + } + + /* Disable Rx DMA to use UART port as wakeup source */ + writeb(readb(sport->port.membase + UARTCR5) & ~UARTCR5_RDMAS, + sport->port.membase + UARTCR5); + } + + if (sport->lpuart_dma_tx_use) { + sport->dma_tx_in_progress = false; + dmaengine_terminate_all(sport->dma_tx_chan); + } + + if (sport->port.suspended && !sport->port.irq_wake) + clk_disable_unprepare(sport->clk); + return 0; } @@ -1933,6 +2183,9 @@ static int lpuart_resume(struct device *dev) struct lpuart_port *sport = dev_get_drvdata(dev); unsigned long temp; + if (sport->port.suspended && !sport->port.irq_wake) + clk_prepare_enable(sport->clk); + if (sport->lpuart32) { lpuart32_setup_watermark(sport); temp = lpuart32_read(sport->port.membase + UARTCTRL); @@ -1946,6 +2199,24 @@ static int lpuart_resume(struct device *dev) writeb(temp, sport->port.membase + UARTCR2); } + if (sport->lpuart_dma_rx_use) { + if (sport->port.irq_wake) { + if (!lpuart_start_rx_dma(sport)) + rx_dma_timer_init(sport); + else + sport->lpuart_dma_rx_use = false; + } + } + + if (sport->dma_tx_chan && !lpuart_dma_tx_request(&sport->port)) { + init_waitqueue_head(&sport->dma_wait); + sport->lpuart_dma_tx_use = true; + writeb(readb(sport->port.membase + UARTCR5) | + UARTCR5_TDMAS, sport->port.membase + UARTCR5); + } else { + sport->lpuart_dma_tx_use = false; + } + uart_resume_port(&lpuart_reg, &sport->port); return 0; diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 858c30814497..7957fb04e283 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -169,6 +169,7 @@ struct hw_bank { * @enabled_otg_timer_bits: bits of enabled otg timers * @next_otg_timer: next nearest enabled timer to be expired * @work: work for role changing + * @work_dr: work for role changing for non-OTG controllers * @wq: workqueue thread * @qh_pool: allocation pool for queue heads * @td_pool: allocation pool for transfer descriptors @@ -215,6 +216,7 @@ struct ci_hdrc { unsigned enabled_otg_timer_bits; enum otg_fsm_timer next_otg_timer; struct work_struct work; + struct work_struct work_dr; struct workqueue_struct *wq; struct dma_pool *qh_pool; diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 2949289bb3c5..dcd9820aa4fb 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -65,6 +65,10 @@ static const struct ci_hdrc_imx_platform_flag imx7d_usb_data = { .flags = CI_HDRC_SUPPORTS_RUNTIME_PM, }; +static const struct ci_hdrc_imx_platform_flag vf610_usb_data = { + .flags = CI_HDRC_DUAL_ROLE_NOT_OTG, +}; + static const struct of_device_id ci_hdrc_imx_dt_ids[] = { { .compatible = "fsl,imx28-usb", .data = &imx28_usb_data}, { .compatible = "fsl,imx27-usb", .data = &imx27_usb_data}, @@ -73,6 +77,7 @@ static const struct of_device_id ci_hdrc_imx_dt_ids[] = { { .compatible = "fsl,imx6sx-usb", .data = &imx6sx_usb_data}, { .compatible = "fsl,imx6ul-usb", .data = &imx6ul_usb_data}, { .compatible = "fsl,imx7d-usb", .data = &imx7d_usb_data}, + { .compatible = "fsl,vf610-usb", .data = &vf610_usb_data}, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids); @@ -301,9 +306,9 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) &pdata); if (IS_ERR(data->ci_pdev)) { ret = PTR_ERR(data->ci_pdev); - dev_err(&pdev->dev, - "Can't register ci_hdrc platform device, err=%d\n", - ret); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "ci_hdrc_add_device failed, err=%d\n", ret); goto err_clk; } @@ -344,6 +349,11 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev) return 0; } +static void ci_hdrc_imx_shutdown(struct platform_device *pdev) +{ + ci_hdrc_imx_remove(pdev); +} + #ifdef CONFIG_PM static int imx_controller_suspend(struct device *dev) { @@ -461,6 +471,7 @@ static const struct dev_pm_ops ci_hdrc_imx_pm_ops = { static struct platform_driver ci_hdrc_imx_driver = { .probe = ci_hdrc_imx_probe, .remove = ci_hdrc_imx_remove, + .shutdown = ci_hdrc_imx_shutdown, .driver = { .name = "imx_usb", .of_match_table = ci_hdrc_imx_dt_ids, diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 57ee43512992..23d9d3eef3d1 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -571,37 +571,71 @@ static irqreturn_t ci_irq(int irq, void *data) return ret; } -static int ci_vbus_notifier(struct notifier_block *nb, unsigned long event, - void *ptr) +static void usb_roleswitch_workqueue(struct work_struct *work) { - struct ci_hdrc_cable *vbus = container_of(nb, struct ci_hdrc_cable, nb); - struct ci_hdrc *ci = vbus->ci; + struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work_dr); + struct ci_hdrc_cable *id, *vbus; + int ret; - if (event) - vbus->state = true; - else - vbus->state = false; + pm_runtime_get_sync(ci->dev); + + id = &ci->platdata->id_extcon; + if (!IS_ERR(id->edev)) { + int new_role; - vbus->changed = true; + ci_role_stop(ci); + hw_wait_phy_stable(); - ci_irq(ci->irq, ci); - return NOTIFY_DONE; + ret = extcon_get_cable_state_(id->edev, EXTCON_USB_HOST); + if (ret) { + new_role = CI_ROLE_HOST; + dev_info(ci->dev, "switching to host role\n"); + } else { + new_role = CI_ROLE_GADGET; + dev_info(ci->dev, "switching to gadget role\n"); + } + ci_role_start(ci, new_role); + } + + vbus = &ci->platdata->vbus_extcon; + if (!IS_ERR(vbus->edev)) { + ret = extcon_get_cable_state_(vbus->edev, EXTCON_USB); + if (ret) { + usb_gadget_vbus_connect(&ci->gadget); + } else { + usb_gadget_vbus_disconnect(&ci->gadget); + } + } + + pm_runtime_put_sync(ci->dev); + + enable_irq(ci->irq); } -static int ci_id_notifier(struct notifier_block *nb, unsigned long event, - void *ptr) +static int ci_cable_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) { - struct ci_hdrc_cable *id = container_of(nb, struct ci_hdrc_cable, nb); - struct ci_hdrc *ci = id->ci; - - if (event) - id->state = false; - else - id->state = true; + struct ci_hdrc_cable *cbl = container_of(nb, struct ci_hdrc_cable, nb); + struct ci_hdrc *ci = cbl->ci; + + if (ci->platdata->flags & CI_HDRC_DUAL_ROLE_NOT_OTG) { + disable_irq_nosync(ci->irq); + + /* + * This notifier might get called twice in succession, + * once for the ID pin and once for the VBUS pin. Make + * sure we only disable irq in case we successfully add + * work to the work queue. + */ + if (!queue_work(system_power_efficient_wq, &ci->work_dr)) + enable_irq(ci->irq); + } else { + cbl->connected = event; + cbl->changed = true; - id->changed = true; + ci_irq(ci->irq, ci); + } - ci_irq(ci->irq, ci); return NOTIFY_DONE; } @@ -718,27 +752,27 @@ static int ci_get_platdata(struct device *dev, } cable = &platdata->vbus_extcon; - cable->nb.notifier_call = ci_vbus_notifier; + cable->nb.notifier_call = ci_cable_notifier; cable->edev = ext_vbus; if (!IS_ERR(ext_vbus)) { ret = extcon_get_cable_state_(cable->edev, EXTCON_USB); if (ret) - cable->state = true; + cable->connected = true; else - cable->state = false; + cable->connected = false; } cable = &platdata->id_extcon; - cable->nb.notifier_call = ci_id_notifier; + cable->nb.notifier_call = ci_cable_notifier; cable->edev = ext_id; if (!IS_ERR(ext_id)) { ret = extcon_get_cable_state_(cable->edev, EXTCON_USB_HOST); if (ret) - cable->state = false; + cable->connected = true; else - cable->state = true; + cable->connected = false; } return 0; } @@ -947,7 +981,15 @@ static int ci_hdrc_probe(struct platform_device *pdev) ci_get_otg_capable(ci); + if (ci->platdata->flags & CI_HDRC_DUAL_ROLE_NOT_OTG) + INIT_WORK(&ci->work_dr, usb_roleswitch_workqueue); + + ret = ci_extcon_register(ci); + if (ret) + goto stop; + dr_mode = ci->platdata->dr_mode; + /* initialize role(s) before the interrupt is requested */ if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { ret = ci_hdrc_host_init(ci); @@ -994,7 +1036,13 @@ static int ci_hdrc_probe(struct platform_device *pdev) * role switch, the defalt role is gadget, and the * user can switch it through debugfs. */ - ci->role = CI_ROLE_GADGET; + struct ci_hdrc_cable *id = &ci->platdata->id_extcon; + if (!IS_ERR(id->edev)) { + if (extcon_get_cable_state_(id->edev, EXTCON_USB_HOST)) + ci->role = CI_ROLE_HOST; + else + ci->role = CI_ROLE_GADGET; + } } } else { ci->role = ci->roles[CI_ROLE_HOST] @@ -1003,9 +1051,20 @@ static int ci_hdrc_probe(struct platform_device *pdev) } if (!ci_otg_is_fsm_mode(ci)) { + /* only update vbus status for peripheral */ - if (ci->role == CI_ROLE_GADGET) - ci_handle_vbus_change(ci); + if (dr_mode == USB_DR_MODE_PERIPHERAL) { + usb_gadget_vbus_connect(&ci->gadget); + } else if (ci->role == CI_ROLE_GADGET) { + struct ci_hdrc_cable *vbus = &ci->platdata->vbus_extcon; + + /* Use vbus state from extcon if provided */ + if (!IS_ERR(vbus->edev) && + extcon_get_cable_state_(vbus->edev, EXTCON_USB)) + usb_gadget_vbus_connect(&ci->gadget); + else + ci_handle_vbus_change(ci); + } ret = ci_role_start(ci, ci->role); if (ret) { @@ -1021,10 +1080,6 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (ret) goto stop; - ret = ci_extcon_register(ci); - if (ret) - goto stop; - if (ci->supports_runtime_pm) { pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index f36a1ac3bfbd..10236fe71522 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -44,7 +44,7 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) else val &= ~OTGSC_BSVIS; - if (cable->state) + if (cable->connected) val |= OTGSC_BSV; else val &= ~OTGSC_BSV; @@ -62,10 +62,10 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) else val &= ~OTGSC_IDIS; - if (cable->state) - val |= OTGSC_ID; + if (cable->connected) + val &= ~OTGSC_ID; /* host */ else - val &= ~OTGSC_ID; + val |= OTGSC_ID; /* device */ if (cable->enabled) val |= OTGSC_IDIE; diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 6abb6a10ee82..41f6f312add0 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -739,7 +739,7 @@ static inline struct gadget_info *os_desc_item_to_gadget_info( static ssize_t os_desc_use_show(struct config_item *item, char *page) { - return sprintf(page, "%d", + return sprintf(page, "%d\n", os_desc_item_to_gadget_info(item)->use_os_desc); } @@ -763,7 +763,7 @@ static ssize_t os_desc_use_store(struct config_item *item, const char *page, static ssize_t os_desc_b_vendor_code_show(struct config_item *item, char *page) { - return sprintf(page, "%d", + return sprintf(page, "0x%02x\n", os_desc_item_to_gadget_info(item)->b_vendor_code); } @@ -788,9 +788,13 @@ static ssize_t os_desc_b_vendor_code_store(struct config_item *item, static ssize_t os_desc_qw_sign_show(struct config_item *item, char *page) { struct gadget_info *gi = os_desc_item_to_gadget_info(item); + int res; + + res = utf16s_to_utf8s((wchar_t *) gi->qw_sign, OS_STRING_QW_SIGN_LEN, + UTF16_LITTLE_ENDIAN, page, PAGE_SIZE - 1); + page[res++] = '\n'; - memcpy(page, gi->qw_sign, OS_STRING_QW_SIGN_LEN); - return OS_STRING_QW_SIGN_LEN; + return res; } static ssize_t os_desc_qw_sign_store(struct config_item *item, const char *page, @@ -902,7 +906,7 @@ static inline struct usb_os_desc_ext_prop static ssize_t ext_prop_type_show(struct config_item *item, char *page) { - return sprintf(page, "%d", to_usb_os_desc_ext_prop(item)->type); + return sprintf(page, "%d\n", to_usb_os_desc_ext_prop(item)->type); } static ssize_t ext_prop_type_store(struct config_item *item, diff --git a/drivers/video/logo/Kconfig b/drivers/video/logo/Kconfig index 0037104d66ac..d2ca63c03524 100644 --- a/drivers/video/logo/Kconfig +++ b/drivers/video/logo/Kconfig @@ -82,4 +82,8 @@ config LOGO_M32R_CLUT224 depends on M32R default y +config LOGO_CUSTOM_CLUT224 + bool "Custom 224-color Linux logo" + default n + endif # LOGO diff --git a/drivers/video/logo/Makefile b/drivers/video/logo/Makefile index 3b437813584c..45d4b5346d07 100644 --- a/drivers/video/logo/Makefile +++ b/drivers/video/logo/Makefile @@ -18,6 +18,8 @@ obj-$(CONFIG_LOGO_M32R_CLUT224) += logo_m32r_clut224.o obj-$(CONFIG_SPU_BASE) += logo_spe_clut224.o +obj-$(CONFIG_LOGO_CUSTOM_CLUT224) += logo_custom_clut224.o + # How to generate logo's # Use logo-cfiles to retrieve list of .c files to be built diff --git a/drivers/video/logo/logo.c b/drivers/video/logo/logo.c index 10fbfd8ab963..bef02e84a076 100644 --- a/drivers/video/logo/logo.c +++ b/drivers/video/logo/logo.c @@ -111,6 +111,10 @@ const struct linux_logo * __init_refok fb_find_logo(int depth) /* M32R Linux logo */ logo = &logo_m32r_clut224; #endif +#ifdef CONFIG_LOGO_CUSTOM_CLUT224 + /* Custom Linux logo */ + logo = &logo_custom_clut224; +#endif } return logo; } |