From e9a03add0c6ed5341fc59ff9c76843c2888a33fa Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Tue, 3 Sep 2013 16:28:59 +0800 Subject: pinctrl: ADI PIN control driver for the GPIO controller on bf54x and bf60x. The new ADI GPIO2 controller was introduced since the BF548 and BF60x processors. It differs a lot from the old one on BF5xx processors. So, create a pinctrl driver under the pinctrl framework. - Define gpio ports and pin interrupt controllers as individual platform devices. - Register a pinctrl driver for the whole GPIO ports and pin interrupt devices. - Probe pint devices before port devices. Put device instances into the global gpio and pint lists. - Define peripheral, irq and gpio reservation bit masks for each gpio port as runtime resources. - Save and restore gpio port and pint status MMRs in syscore PM functions. - Create the plug-in subdrivers to hold the pinctrl soc data for bf54x and bf60x. Add soc data into struct adi_pinctrl. Initialize the soc data in pin controller probe function. Get the pin groups and functions via the soc data reference. - Call gpiochip_add_pin_range() in gpio device probe function to register range cross reference between gpio device and pin control device. - Get range by pinctrl_find_gpio_range_from_pin(), find gpio_port object by container_of() and find adi_pinctrl by pin control device name. - Handle peripheral and gpio requests in pinctrl operation functions. - Demux gpio IRQs via the irq_domain created by each GPIO port. v2-changes: - Remove unlinke() directive. v3-changes: - Rename struct adi_pmx to adi_pinctrl. - Fix the comments of struct gpio_pint. - Remove unused pin_base in struct gpio_port. - Change pint_assign into bool type. - Add comments about the relationship between pint device and port device to the driver header. - Use BIT macro to shift bit. - Remove all bitmap reservation help functions. Inline reservation functions into the actual code. - Remove gpio and offset mutual reference help functions. - Remove all help functions to find gpio_port and adi_pinctrl structs. Get range by pinctrl_find_gpio_range_from_pin(), find gpio_port object by container_of() and find adi_pinctrl by pin control device name. - Pass bool type usage variable to port_setup help function. - Separate long bit operations into several lines and add comments. - Use debugfs to output all GPIO request information. - Avoid to set drvdata to NULL - Add explanation to function adi_gpio_init_int() - Call gpiochip_add_pin_range() in gpio device probe function to register range cross reference between gpio device and pin control device. - Remove the reference to pin control device from the gpio_port struct. Remove the reference list to gpio device from the adi_pinctrl struct. Replace the global adi_pinctrl list with adi_gpio_port_list. Walk through the gpio list to do power suspend and resume operations. - Remove the global GPIO base from struct adi_pinctrl, define pin base in the platform data for each GPIO port device. - Initialize adi_pinctrl_setup in arch_initcall(). - print the status of triggers, whether it is in GPIO mode, if it is flagged to be used as IRQ, etc in adi_pin_dbg_show(). - Create the plug-in subdrivers to hold the pinctrl soc data for bf54x and bf60x. Add soc data into struct adi_pinctrl. Initialize the soc data in pin controller probe function. Get the pin groups and functions via the soc data reference. v4-changes: - remove useless system_state checking. - replace dev_err with dev_warn in both irq and gpio pin cases. - comment on relationship between irq type and invert operation. - It is not necessary to check the reservation mode of the requested pin in IRQ chip operation. Remove the reservation map. - Use existing gpio/pinctrl subsystem debugfs files. Remove pinctrl-adi2 driver specific debugfs output. - Add linkport group and function information for bf60x. - Separate uart and ctsrts pins into 2 groups. - Separate APAPI and alternative ATAPI pins into 2 groups. Signed-off-by: Sonic Zhang Signed-off-by: Linus Walleij --- include/linux/platform_data/pinctrl-adi2.h | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 include/linux/platform_data/pinctrl-adi2.h (limited to 'include/linux/platform_data') diff --git a/include/linux/platform_data/pinctrl-adi2.h b/include/linux/platform_data/pinctrl-adi2.h new file mode 100644 index 000000000000..8f91300617ec --- /dev/null +++ b/include/linux/platform_data/pinctrl-adi2.h @@ -0,0 +1,40 @@ +/* + * Pinctrl Driver for ADI GPIO2 controller + * + * Copyright 2007-2013 Analog Devices Inc. + * + * Licensed under the GPLv2 or later + */ + + +#ifndef PINCTRL_ADI2_H +#define PINCTRL_ADI2_H + +#include +#include + +/** + * struct adi_pinctrl_gpio_platform_data - Pinctrl gpio platform data + * for ADI GPIO2 device. + * + * @port_gpio_base: Optional global GPIO index of the GPIO bank. + * 0 means driver decides. + * @port_pin_base: Pin index of the pin controller device. + * @port_width: PIN number of the GPIO bank device + * @pint_id: GPIO PINT device id that this GPIO bank should map to. + * @pint_assign: The 32-bit GPIO PINT registers can be divided into 2 parts. A + * GPIO bank can be mapped into either low 16 bits[0] or high 16 + * bits[1] of each PINT register. + * @pint_map: GIOP bank mapping code in PINT device + */ +struct adi_pinctrl_gpio_platform_data { + unsigned int port_gpio_base; + unsigned int port_pin_base; + unsigned int port_width; + u8 pinctrl_id; + u8 pint_id; + bool pint_assign; + u8 pint_map; +}; + +#endif -- cgit v1.2.3 From 118150f22d6b4431a1fe2e715de314a5d93836f5 Mon Sep 17 00:00:00 2001 From: KV Sujith Date: Sun, 18 Aug 2013 10:48:58 +0530 Subject: gpio: davinci: move to platform device Modify DaVinci GPIO driver to become a platform device driver. The driver does not have platform driver structure or a probe. Instead, it has pure_initcall function for initialization. The platform specific informaiton is obtained using the DaVinci specific davinci_soc_info structure. This is a problem for Device Tree (DT) implementation. As a first stage of DT conversion, we implement a probe. Additional notes: - The driver registration happens as postcore_initcall. This is required since machine init functions like da850_lcd_hw_init() make use of GPIO. - Start using devres APIs for simpler error handling. Signed-off-by: KV Sujith [avinashphilip@ti.com: Move global definition of "davinci_gpio_controller" to local] Signed-off-by: Philip Avinash Acked-by: Linus Walleij [nsekhar@ti.com: drop unused structure member, rebase to new clean-up patch and fix error messages] Signed-off-by: Sekhar Nori Signed-off-by: Lad, Prabhakar --- arch/arm/mach-davinci/include/mach/gpio-davinci.h | 1 + drivers/gpio/gpio-davinci.c | 119 +++++++++++++++------- include/linux/platform_data/gpio-davinci.h | 25 +++++ 3 files changed, 107 insertions(+), 38 deletions(-) create mode 100644 include/linux/platform_data/gpio-davinci.h (limited to 'include/linux/platform_data') diff --git a/arch/arm/mach-davinci/include/mach/gpio-davinci.h b/arch/arm/mach-davinci/include/mach/gpio-davinci.h index 1fdd1fd35448..551ba43a763e 100644 --- a/arch/arm/mach-davinci/include/mach/gpio-davinci.h +++ b/arch/arm/mach-davinci/include/mach/gpio-davinci.h @@ -60,6 +60,7 @@ struct davinci_gpio_controller { void __iomem *set_data; void __iomem *clr_data; void __iomem *in_data; + unsigned gpio_irq; }; /* The __gpio_to_controller() and __gpio_mask() functions inline to constants diff --git a/drivers/gpio/gpio-davinci.c b/drivers/gpio/gpio-davinci.c index cb947a1266ae..8847adf392b7 100644 --- a/drivers/gpio/gpio-davinci.c +++ b/drivers/gpio/gpio-davinci.c @@ -15,8 +15,9 @@ #include #include #include - -#include +#include +#include +#include struct davinci_gpio_regs { u32 dir; @@ -36,10 +37,9 @@ struct davinci_gpio_regs { #define chip2controller(chip) \ container_of(chip, struct davinci_gpio_controller, chip) -static struct davinci_gpio_controller chips[DIV_ROUND_UP(DAVINCI_N_GPIO, 32)]; static void __iomem *gpio_base; -static struct davinci_gpio_regs __iomem __init *gpio2regs(unsigned gpio) +static struct davinci_gpio_regs __iomem *gpio2regs(unsigned gpio) { void __iomem *ptr; @@ -67,7 +67,7 @@ static inline struct davinci_gpio_regs __iomem *irq2regs(int irq) return g; } -static int __init davinci_gpio_irq_setup(void); +static int davinci_gpio_irq_setup(struct platform_device *pdev); /*--------------------------------------------------------------------------*/ @@ -133,33 +133,53 @@ davinci_gpio_set(struct gpio_chip *chip, unsigned offset, int value) __raw_writel((1 << offset), value ? &g->set_data : &g->clr_data); } -static int __init davinci_gpio_setup(void) +static int davinci_gpio_probe(struct platform_device *pdev) { int i, base; unsigned ngpio; - struct davinci_soc_info *soc_info = &davinci_soc_info; - struct davinci_gpio_regs *regs; - - if (soc_info->gpio_type != GPIO_TYPE_DAVINCI) - return 0; + struct davinci_gpio_controller *chips; + struct davinci_gpio_platform_data *pdata; + struct davinci_gpio_regs __iomem *regs; + struct device *dev = &pdev->dev; + struct resource *res; + + pdata = dev->platform_data; + if (!pdata) { + dev_err(dev, "No platform data found\n"); + return -EINVAL; + } /* * The gpio banks conceptually expose a segmented bitmap, * and "ngpio" is one more than the largest zero-based * bit index that's valid. */ - ngpio = soc_info->gpio_num; + ngpio = pdata->ngpio; if (ngpio == 0) { - pr_err("GPIO setup: how many GPIOs?\n"); + dev_err(dev, "How many GPIOs?\n"); return -EINVAL; } if (WARN_ON(DAVINCI_N_GPIO < ngpio)) ngpio = DAVINCI_N_GPIO; - gpio_base = ioremap(soc_info->gpio_base, SZ_4K); - if (WARN_ON(!gpio_base)) + chips = devm_kzalloc(dev, + ngpio * sizeof(struct davinci_gpio_controller), + GFP_KERNEL); + if (!chips) { + dev_err(dev, "Memory allocation failed\n"); return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "Invalid memory resource\n"); + return -EBUSY; + } + + gpio_base = devm_ioremap_resource(dev, res); + if (IS_ERR(gpio_base)) + return PTR_ERR(gpio_base); for (i = 0, base = 0; base < ngpio; i++, base += 32) { chips[i].chip.label = "DaVinci"; @@ -185,13 +205,10 @@ static int __init davinci_gpio_setup(void) gpiochip_add(&chips[i].chip); } - soc_info->gpio_ctlrs = chips; - soc_info->gpio_ctlrs_num = DIV_ROUND_UP(ngpio, 32); - - davinci_gpio_irq_setup(); + platform_set_drvdata(pdev, chips); + davinci_gpio_irq_setup(pdev); return 0; } -pure_initcall(davinci_gpio_setup); /*--------------------------------------------------------------------------*/ /* @@ -304,14 +321,14 @@ static int gpio_to_irq_banked(struct gpio_chip *chip, unsigned offset) static int gpio_to_irq_unbanked(struct gpio_chip *chip, unsigned offset) { - struct davinci_soc_info *soc_info = &davinci_soc_info; + struct davinci_gpio_controller *d = chip2controller(chip); /* * NOTE: we assume for now that only irqs in the first gpio_chip * can provide direct-mapped IRQs to AINTC (up to 32 GPIOs). */ - if (offset < soc_info->gpio_unbanked) - return soc_info->gpio_irq + offset; + if (offset < d->irq_base) + return d->gpio_irq + offset; else return -ENODEV; } @@ -320,12 +337,11 @@ static int gpio_irq_type_unbanked(struct irq_data *data, unsigned trigger) { struct davinci_gpio_controller *d; struct davinci_gpio_regs __iomem *g; - struct davinci_soc_info *soc_info = &davinci_soc_info; u32 mask; d = (struct davinci_gpio_controller *)data->handler_data; g = (struct davinci_gpio_regs __iomem *)d->regs; - mask = __gpio_mask(data->irq - soc_info->gpio_irq); + mask = __gpio_mask(data->irq - d->gpio_irq); if (trigger & ~(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) return -EINVAL; @@ -346,24 +362,33 @@ static int gpio_irq_type_unbanked(struct irq_data *data, unsigned trigger) * (dm6446) can be set appropriately for GPIOV33 pins. */ -static int __init davinci_gpio_irq_setup(void) +static int davinci_gpio_irq_setup(struct platform_device *pdev) { unsigned gpio, irq, bank; struct clk *clk; u32 binten = 0; unsigned ngpio, bank_irq; - struct davinci_soc_info *soc_info = &davinci_soc_info; - struct davinci_gpio_regs __iomem *g; + struct device *dev = &pdev->dev; + struct resource *res; + struct davinci_gpio_controller *chips = platform_get_drvdata(pdev); + struct davinci_gpio_platform_data *pdata = dev->platform_data; + struct davinci_gpio_regs __iomem *g; - ngpio = soc_info->gpio_num; + ngpio = pdata->ngpio; + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(dev, "Invalid IRQ resource\n"); + return -EBUSY; + } - bank_irq = soc_info->gpio_irq; - if (bank_irq == 0) { - printk(KERN_ERR "Don't know first GPIO bank IRQ.\n"); - return -EINVAL; + bank_irq = res->start; + + if (!bank_irq) { + dev_err(dev, "Invalid IRQ resource\n"); + return -ENODEV; } - clk = clk_get(NULL, "gpio"); + clk = devm_clk_get(dev, "gpio"); if (IS_ERR(clk)) { printk(KERN_ERR "Error %ld getting gpio clock?\n", PTR_ERR(clk)); @@ -379,9 +404,9 @@ static int __init davinci_gpio_irq_setup(void) */ for (gpio = 0, bank = 0; gpio < ngpio; bank++, gpio += 32) { chips[bank].chip.to_irq = gpio_to_irq_banked; - chips[bank].irq_base = soc_info->gpio_unbanked + chips[bank].irq_base = pdata->gpio_unbanked ? -EINVAL - : (soc_info->intc_irq_num + gpio); + : (pdata->intc_irq_num + gpio); } /* @@ -389,7 +414,7 @@ static int __init davinci_gpio_irq_setup(void) * controller only handling trigger modes. We currently assume no * IRQ mux conflicts; gpio_irq_type_unbanked() is only for GPIOs. */ - if (soc_info->gpio_unbanked) { + if (pdata->gpio_unbanked) { static struct irq_chip_type gpio_unbanked; /* pass "bank 0" GPIO IRQs to AINTC */ @@ -409,7 +434,7 @@ static int __init davinci_gpio_irq_setup(void) __raw_writel(~0, &g->set_rising); /* set the direct IRQs up to use that irqchip */ - for (gpio = 0; gpio < soc_info->gpio_unbanked; gpio++, irq++) { + for (gpio = 0; gpio < pdata->gpio_unbanked; gpio++, irq++) { irq_set_chip(irq, &gpio_unbanked.chip); irq_set_handler_data(irq, &chips[gpio / 32]); irq_set_status_flags(irq, IRQ_TYPE_EDGE_BOTH); @@ -464,3 +489,21 @@ done: return 0; } + +static struct platform_driver davinci_gpio_driver = { + .probe = davinci_gpio_probe, + .driver = { + .name = "davinci_gpio", + .owner = THIS_MODULE, + }, +}; + +/** + * GPIO driver registration needs to be done before machine_init functions + * access GPIO. Hence davinci_gpio_drv_reg() is a postcore_initcall. + */ +static int __init davinci_gpio_drv_reg(void) +{ + return platform_driver_register(&davinci_gpio_driver); +} +postcore_initcall(davinci_gpio_drv_reg); diff --git a/include/linux/platform_data/gpio-davinci.h b/include/linux/platform_data/gpio-davinci.h new file mode 100644 index 000000000000..2fcc125af1aa --- /dev/null +++ b/include/linux/platform_data/gpio-davinci.h @@ -0,0 +1,25 @@ +/* + * DaVinci GPIO Platform Related Defines + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __DAVINCI_GPIO_PLATFORM_H +#define __DAVINCI_GPIO_PLATFORM_H + +struct davinci_gpio_platform_data { + u32 ngpio; + u32 gpio_unbanked; + u32 intc_irq_num; +}; + +#endif -- cgit v1.2.3 From f1a4c52ff5913378b7baf05ac71f10282b341cf7 Mon Sep 17 00:00:00 2001 From: Philip Avinash Date: Sun, 18 Aug 2013 10:49:03 +0530 Subject: ARM: davinci: gpio: use gpiolib API instead of inline functions Remove NEED_MACH_GPIO_H config select option for ARCH_DAVINCI to start using gpiolib interface for davinci platforms. This makes it easier to use the gpio driver on other platforms as it breaks dependency on mach-davinci. Latencies for gpio_get/set APIs will increase. On measurement, latency was found to have increased by 18 microsecond with gpiolib API as compared to inline APIs. Measurement was done on DA850 EVM for gpio_get_value() API by taking the printk timing across the call with interrupts disabled. inline gpio API with interrupt disabled [ 29.734337] before gpio_get [ 29.736847] after gpio_get Time difference 0.00251 gpio library with interrupt disabled [ 272.876763] before gpio_get [ 272.879291] after gpio_get Time difference 0.002528 Latency increased by (0.002528 - 0.00251) = 18 microsecond. While at it, remove GPIO_TYPE_DAVINCI enum definition as gpio-davinci.c is converted to Linux device driver model. Signed-off-by: Philip Avinash Signed-off-by: Lad, Prabhakar Acked-by: Linus Walleij [nsekhar@ti.com: minor edits to commit message] Signed-off-by: Sekhar Nori --- arch/arm/Kconfig | 1 - arch/arm/mach-davinci/include/mach/gpio-davinci.h | 90 ----------------------- arch/arm/mach-davinci/include/mach/gpio.h | 88 ---------------------- drivers/gpio/gpio-tnetv107x.c | 1 + include/linux/platform_data/gpio-davinci.h | 35 +++++++++ 5 files changed, 36 insertions(+), 179 deletions(-) delete mode 100644 arch/arm/mach-davinci/include/mach/gpio-davinci.h delete mode 100644 arch/arm/mach-davinci/include/mach/gpio.h (limited to 'include/linux/platform_data') diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 3f7714d8d2d2..ad3767095422 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -847,7 +847,6 @@ config ARCH_DAVINCI select GENERIC_CLOCKEVENTS select GENERIC_IRQ_CHIP select HAVE_IDE - select NEED_MACH_GPIO_H select TI_PRIV_EDMA select USE_OF select ZONE_DMA diff --git a/arch/arm/mach-davinci/include/mach/gpio-davinci.h b/arch/arm/mach-davinci/include/mach/gpio-davinci.h deleted file mode 100644 index 0d63b24cefc9..000000000000 --- a/arch/arm/mach-davinci/include/mach/gpio-davinci.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * TI DaVinci GPIO Support - * - * Copyright (c) 2006 David Brownell - * Copyright (c) 2007, MontaVista Software, 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 __DAVINCI_DAVINCI_GPIO_H -#define __DAVINCI_DAVINCI_GPIO_H - -#include -#include - -#include - -#include -#include - -enum davinci_gpio_type { - GPIO_TYPE_DAVINCI = 0, - GPIO_TYPE_TNETV107X, -}; - -/* - * basic gpio routines - * - * board-specific init should be done by arch/.../.../board-XXX.c (maybe - * initializing banks together) rather than boot loaders; kexec() won't - * go through boot loaders. - * - * the gpio clock will be turned on when gpios are used, and you may also - * need to pay attention to PINMUX registers to be sure those pins are - * used as gpios, not with other peripherals. - * - * On-chip GPIOs are numbered 0..(DAVINCI_N_GPIO-1). For documentation, - * and maybe for later updates, code may write GPIO(N). These may be - * all 1.8V signals, all 3.3V ones, or a mix of the two. A given chip - * may not support all the GPIOs in that range. - * - * GPIOs can also be on external chips, numbered after the ones built-in - * to the DaVinci chip. For now, they won't be usable as IRQ sources. - */ -#define GPIO(X) (X) /* 0 <= X <= (DAVINCI_N_GPIO - 1) */ - -/* Convert GPIO signal to GPIO pin number */ -#define GPIO_TO_PIN(bank, gpio) (16 * (bank) + (gpio)) - -struct davinci_gpio_controller { - struct gpio_chip chip; - int irq_base; - spinlock_t lock; - void __iomem *regs; - void __iomem *set_data; - void __iomem *clr_data; - void __iomem *in_data; - unsigned gpio_irq; -}; - -/* The __gpio_to_controller() and __gpio_mask() functions inline to constants - * with constant parameters; or in outlined code they execute at runtime. - * - * You'd access the controller directly when reading or writing more than - * one gpio value at a time, and to support wired logic where the value - * being driven by the cpu need not match the value read back. - * - * These are NOT part of the cross-platform GPIO interface - */ -static inline struct davinci_gpio_controller * -__gpio_to_controller(unsigned gpio) -{ - struct davinci_gpio_controller *ctlrs = davinci_soc_info.gpio_ctlrs; - int index = gpio / 32; - - if (!ctlrs || index >= davinci_soc_info.gpio_ctlrs_num) - return NULL; - - return ctlrs + index; -} - -static inline u32 __gpio_mask(unsigned gpio) -{ - return 1 << (gpio % 32); -} - -#endif /* __DAVINCI_DAVINCI_GPIO_H */ diff --git a/arch/arm/mach-davinci/include/mach/gpio.h b/arch/arm/mach-davinci/include/mach/gpio.h deleted file mode 100644 index 960e9de47e1e..000000000000 --- a/arch/arm/mach-davinci/include/mach/gpio.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * TI DaVinci GPIO Support - * - * Copyright (c) 2006 David Brownell - * Copyright (c) 2007, MontaVista Software, 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 __DAVINCI_GPIO_H -#define __DAVINCI_GPIO_H - -#include - -#define __ARM_GPIOLIB_COMPLEX - -/* The inline versions use the static inlines in the driver header */ -#include "gpio-davinci.h" - -/* - * The get/set/clear functions will inline when called with constant - * parameters referencing built-in GPIOs, for low-overhead bitbanging. - * - * gpio_set_value() will inline only on traditional Davinci style controllers - * with distinct set/clear registers. - * - * Otherwise, calls with variable parameters or referencing external - * GPIOs (e.g. on GPIO expander chips) use outlined functions. - */ -static inline void gpio_set_value(unsigned gpio, int value) -{ - if (__builtin_constant_p(value) && gpio < davinci_soc_info.gpio_num) { - struct davinci_gpio_controller *ctlr; - u32 mask; - - ctlr = __gpio_to_controller(gpio); - - if (ctlr->set_data != ctlr->clr_data) { - mask = __gpio_mask(gpio); - if (value) - __raw_writel(mask, ctlr->set_data); - else - __raw_writel(mask, ctlr->clr_data); - return; - } - } - - __gpio_set_value(gpio, value); -} - -/* Returns zero or nonzero; works for gpios configured as inputs OR - * as outputs, at least for built-in GPIOs. - * - * NOTE: for built-in GPIOs, changes in reported values are synchronized - * to the GPIO clock. This is easily seen after calling gpio_set_value() - * and then immediately gpio_get_value(), where the gpio_get_value() will - * return the old value until the GPIO clock ticks and the new value gets - * latched. - */ -static inline int gpio_get_value(unsigned gpio) -{ - struct davinci_gpio_controller *ctlr; - - if (!__builtin_constant_p(gpio) || gpio >= davinci_soc_info.gpio_num) - return __gpio_get_value(gpio); - - ctlr = __gpio_to_controller(gpio); - return __gpio_mask(gpio) & __raw_readl(ctlr->in_data); -} - -static inline int gpio_cansleep(unsigned gpio) -{ - if (__builtin_constant_p(gpio) && gpio < davinci_soc_info.gpio_num) - return 0; - else - return __gpio_cansleep(gpio); -} - -static inline int irq_to_gpio(unsigned irq) -{ - /* don't support the reverse mapping */ - return -ENOSYS; -} - -#endif /* __DAVINCI_GPIO_H */ diff --git a/drivers/gpio/gpio-tnetv107x.c b/drivers/gpio/gpio-tnetv107x.c index 3fa3e2867e19..58445bb69106 100644 --- a/drivers/gpio/gpio-tnetv107x.c +++ b/drivers/gpio/gpio-tnetv107x.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include diff --git a/include/linux/platform_data/gpio-davinci.h b/include/linux/platform_data/gpio-davinci.h index 2fcc125af1aa..6efd20264585 100644 --- a/include/linux/platform_data/gpio-davinci.h +++ b/include/linux/platform_data/gpio-davinci.h @@ -16,10 +16,45 @@ #ifndef __DAVINCI_GPIO_PLATFORM_H #define __DAVINCI_GPIO_PLATFORM_H +#include +#include + +#include + +enum davinci_gpio_type { + GPIO_TYPE_TNETV107X = 0, +}; + struct davinci_gpio_platform_data { u32 ngpio; u32 gpio_unbanked; u32 intc_irq_num; }; + +struct davinci_gpio_controller { + struct gpio_chip chip; + int irq_base; + /* Serialize access to GPIO registers */ + spinlock_t lock; + void __iomem *regs; + void __iomem *set_data; + void __iomem *clr_data; + void __iomem *in_data; + int gpio_unbanked; + unsigned gpio_irq; +}; + +/* + * basic gpio routines + */ +#define GPIO(X) (X) /* 0 <= X <= (DAVINCI_N_GPIO - 1) */ + +/* Convert GPIO signal to GPIO pin number */ +#define GPIO_TO_PIN(bank, gpio) (16 * (bank) + (gpio)) + +static inline u32 __gpio_mask(unsigned gpio) +{ + return 1 << (gpio % 32); +} #endif -- cgit v1.2.3 From 82b0f4b7c576d22c764239662cedc63c21f02d8d Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Tue, 17 Sep 2013 10:11:53 +0100 Subject: clk: ux500: Copy u8500_clk_init() ready for DT enablement Here we're using the old clock initialisation function as a template. It's necessary to remove all of the clk_register_clkdev() calls as they don't make sense when booting with Device Tree. Cc: Mike Turquette Signed-off-by: Lee Jones Signed-off-by: Linus Walleij --- drivers/clk/ux500/Makefile | 1 + drivers/clk/ux500/u8500_of_clk.c | 381 ++++++++++++++++++++++++++++++++ include/linux/platform_data/clk-ux500.h | 3 + 3 files changed, 385 insertions(+) create mode 100644 drivers/clk/ux500/u8500_of_clk.c (limited to 'include/linux/platform_data') diff --git a/drivers/clk/ux500/Makefile b/drivers/clk/ux500/Makefile index c6a806ed0e8c..521483f0ba33 100644 --- a/drivers/clk/ux500/Makefile +++ b/drivers/clk/ux500/Makefile @@ -8,6 +8,7 @@ obj-y += clk-prcmu.o obj-y += clk-sysctrl.o # Clock definitions +obj-y += u8500_of_clk.o obj-y += u8500_clk.o obj-y += u9540_clk.o obj-y += u8540_clk.o diff --git a/drivers/clk/ux500/u8500_of_clk.c b/drivers/clk/ux500/u8500_of_clk.c new file mode 100644 index 000000000000..ceebce6a624f --- /dev/null +++ b/drivers/clk/ux500/u8500_of_clk.c @@ -0,0 +1,381 @@ +/* + * Clock definitions for u8500 platform. + * + * Copyright (C) 2012 ST-Ericsson SA + * Author: Ulf Hansson + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include +#include "clk.h" + +void u8500_of_clk_init(u32 clkrst1_base, u32 clkrst2_base, u32 clkrst3_base, + u32 clkrst5_base, u32 clkrst6_base) +{ + struct prcmu_fw_version *fw_version; + const char *sgaclk_parent = NULL; + struct clk *clk; + + /* Clock sources */ + clk = clk_reg_prcmu_gate("soc0_pll", NULL, PRCMU_PLLSOC0, + CLK_IS_ROOT|CLK_IGNORE_UNUSED); + + clk = clk_reg_prcmu_gate("soc1_pll", NULL, PRCMU_PLLSOC1, + CLK_IS_ROOT|CLK_IGNORE_UNUSED); + + clk = clk_reg_prcmu_gate("ddr_pll", NULL, PRCMU_PLLDDR, + CLK_IS_ROOT|CLK_IGNORE_UNUSED); + + /* FIXME: Add sys, ulp and int clocks here. */ + + clk = clk_register_fixed_rate(NULL, "rtc32k", "NULL", + CLK_IS_ROOT|CLK_IGNORE_UNUSED, + 32768); + + /* PRCMU clocks */ + fw_version = prcmu_get_fw_version(); + if (fw_version != NULL) { + switch (fw_version->project) { + case PRCMU_FW_PROJECT_U8500_C2: + case PRCMU_FW_PROJECT_U8520: + case PRCMU_FW_PROJECT_U8420: + sgaclk_parent = "soc0_pll"; + break; + default: + break; + } + } + + if (sgaclk_parent) + clk = clk_reg_prcmu_gate("sgclk", sgaclk_parent, + PRCMU_SGACLK, 0); + else + clk = clk_reg_prcmu_gate("sgclk", NULL, + PRCMU_SGACLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("uartclk", NULL, PRCMU_UARTCLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("msp02clk", NULL, PRCMU_MSP02CLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("msp1clk", NULL, PRCMU_MSP1CLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("i2cclk", NULL, PRCMU_I2CCLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("slimclk", NULL, PRCMU_SLIMCLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("per1clk", NULL, PRCMU_PER1CLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("per2clk", NULL, PRCMU_PER2CLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("per3clk", NULL, PRCMU_PER3CLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("per5clk", NULL, PRCMU_PER5CLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("per6clk", NULL, PRCMU_PER6CLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("per7clk", NULL, PRCMU_PER7CLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_scalable("lcdclk", NULL, PRCMU_LCDCLK, 0, + CLK_IS_ROOT|CLK_SET_RATE_GATE); + + clk = clk_reg_prcmu_opp_gate("bmlclk", NULL, PRCMU_BMLCLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_scalable("hsitxclk", NULL, PRCMU_HSITXCLK, 0, + CLK_IS_ROOT|CLK_SET_RATE_GATE); + + clk = clk_reg_prcmu_scalable("hsirxclk", NULL, PRCMU_HSIRXCLK, 0, + CLK_IS_ROOT|CLK_SET_RATE_GATE); + + clk = clk_reg_prcmu_scalable("hdmiclk", NULL, PRCMU_HDMICLK, 0, + CLK_IS_ROOT|CLK_SET_RATE_GATE); + + clk = clk_reg_prcmu_gate("apeatclk", NULL, PRCMU_APEATCLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("apetraceclk", NULL, PRCMU_APETRACECLK, + CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("mcdeclk", NULL, PRCMU_MCDECLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_opp_gate("ipi2cclk", NULL, PRCMU_IPI2CCLK, + CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("dsialtclk", NULL, PRCMU_DSIALTCLK, + CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("dmaclk", NULL, PRCMU_DMACLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("b2r2clk", NULL, PRCMU_B2R2CLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_scalable("tvclk", NULL, PRCMU_TVCLK, 0, + CLK_IS_ROOT|CLK_SET_RATE_GATE); + + clk = clk_reg_prcmu_gate("sspclk", NULL, PRCMU_SSPCLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("rngclk", NULL, PRCMU_RNGCLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("uiccclk", NULL, PRCMU_UICCCLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_gate("timclk", NULL, PRCMU_TIMCLK, CLK_IS_ROOT); + + clk = clk_reg_prcmu_opp_volt_scalable("sdmmcclk", NULL, PRCMU_SDMMCCLK, + 100000000, + CLK_IS_ROOT|CLK_SET_RATE_GATE); + + clk = clk_reg_prcmu_scalable("dsi_pll", "hdmiclk", + PRCMU_PLLDSI, 0, CLK_SET_RATE_GATE); + + + clk = clk_reg_prcmu_scalable("dsi0clk", "dsi_pll", + PRCMU_DSI0CLK, 0, CLK_SET_RATE_GATE); + + clk = clk_reg_prcmu_scalable("dsi1clk", "dsi_pll", + PRCMU_DSI1CLK, 0, CLK_SET_RATE_GATE); + + clk = clk_reg_prcmu_scalable("dsi0escclk", "tvclk", + PRCMU_DSI0ESCCLK, 0, CLK_SET_RATE_GATE); + + clk = clk_reg_prcmu_scalable("dsi1escclk", "tvclk", + PRCMU_DSI1ESCCLK, 0, CLK_SET_RATE_GATE); + + clk = clk_reg_prcmu_scalable("dsi2escclk", "tvclk", + PRCMU_DSI2ESCCLK, 0, CLK_SET_RATE_GATE); + + clk = clk_reg_prcmu_scalable_rate("armss", NULL, + PRCMU_ARMSS, 0, CLK_IS_ROOT|CLK_IGNORE_UNUSED); + + clk = clk_register_fixed_factor(NULL, "smp_twd", "armss", + CLK_IGNORE_UNUSED, 1, 2); + + /* + * FIXME: Add special handled PRCMU clocks here: + * 1. clkout0yuv, use PRCMU as parent + need regulator + pinctrl. + * 2. ab9540_clkout1yuv, see clkout0yuv + */ + + /* PRCC P-clocks */ + clk = clk_reg_prcc_pclk("p1_pclk0", "per1clk", clkrst1_base, + BIT(0), 0); + + clk = clk_reg_prcc_pclk("p1_pclk1", "per1clk", clkrst1_base, + BIT(1), 0); + + clk = clk_reg_prcc_pclk("p1_pclk2", "per1clk", clkrst1_base, + BIT(2), 0); + + clk = clk_reg_prcc_pclk("p1_pclk3", "per1clk", clkrst1_base, + BIT(3), 0); + + clk = clk_reg_prcc_pclk("p1_pclk4", "per1clk", clkrst1_base, + BIT(4), 0); + + clk = clk_reg_prcc_pclk("p1_pclk5", "per1clk", clkrst1_base, + BIT(5), 0); + + clk = clk_reg_prcc_pclk("p1_pclk6", "per1clk", clkrst1_base, + BIT(6), 0); + + clk = clk_reg_prcc_pclk("p1_pclk7", "per1clk", clkrst1_base, + BIT(7), 0); + + clk = clk_reg_prcc_pclk("p1_pclk8", "per1clk", clkrst1_base, + BIT(8), 0); + + clk = clk_reg_prcc_pclk("p1_pclk9", "per1clk", clkrst1_base, + BIT(9), 0); + + clk = clk_reg_prcc_pclk("p1_pclk10", "per1clk", clkrst1_base, + BIT(10), 0); + + clk = clk_reg_prcc_pclk("p1_pclk11", "per1clk", clkrst1_base, + BIT(11), 0); + + clk = clk_reg_prcc_pclk("p2_pclk0", "per2clk", clkrst2_base, + BIT(0), 0); + + clk = clk_reg_prcc_pclk("p2_pclk1", "per2clk", clkrst2_base, + BIT(1), 0); + + clk = clk_reg_prcc_pclk("p2_pclk2", "per2clk", clkrst2_base, + BIT(2), 0); + + clk = clk_reg_prcc_pclk("p2_pclk3", "per2clk", clkrst2_base, + BIT(3), 0); + + clk = clk_reg_prcc_pclk("p2_pclk4", "per2clk", clkrst2_base, + BIT(4), 0); + + clk = clk_reg_prcc_pclk("p2_pclk5", "per2clk", clkrst2_base, + BIT(5), 0); + + clk = clk_reg_prcc_pclk("p2_pclk6", "per2clk", clkrst2_base, + BIT(6), 0); + + clk = clk_reg_prcc_pclk("p2_pclk7", "per2clk", clkrst2_base, + BIT(7), 0); + + clk = clk_reg_prcc_pclk("p2_pclk8", "per2clk", clkrst2_base, + BIT(8), 0); + + clk = clk_reg_prcc_pclk("p2_pclk9", "per2clk", clkrst2_base, + BIT(9), 0); + + clk = clk_reg_prcc_pclk("p2_pclk10", "per2clk", clkrst2_base, + BIT(10), 0); + + clk = clk_reg_prcc_pclk("p2_pclk11", "per2clk", clkrst2_base, + BIT(11), 0); + + clk = clk_reg_prcc_pclk("p2_pclk12", "per2clk", clkrst2_base, + BIT(12), 0); + + clk = clk_reg_prcc_pclk("p3_pclk0", "per3clk", clkrst3_base, + BIT(0), 0); + + clk = clk_reg_prcc_pclk("p3_pclk1", "per3clk", clkrst3_base, + BIT(1), 0); + + clk = clk_reg_prcc_pclk("p3_pclk2", "per3clk", clkrst3_base, + BIT(2), 0); + + clk = clk_reg_prcc_pclk("p3_pclk3", "per3clk", clkrst3_base, + BIT(3), 0); + + clk = clk_reg_prcc_pclk("p3_pclk4", "per3clk", clkrst3_base, + BIT(4), 0); + + clk = clk_reg_prcc_pclk("p3_pclk5", "per3clk", clkrst3_base, + BIT(5), 0); + + clk = clk_reg_prcc_pclk("p3_pclk6", "per3clk", clkrst3_base, + BIT(6), 0); + + clk = clk_reg_prcc_pclk("p3_pclk7", "per3clk", clkrst3_base, + BIT(7), 0); + + clk = clk_reg_prcc_pclk("p3_pclk8", "per3clk", clkrst3_base, + BIT(8), 0); + + clk = clk_reg_prcc_pclk("p5_pclk0", "per5clk", clkrst5_base, + BIT(0), 0); + + clk = clk_reg_prcc_pclk("p5_pclk1", "per5clk", clkrst5_base, + BIT(1), 0); + + clk = clk_reg_prcc_pclk("p6_pclk0", "per6clk", clkrst6_base, + BIT(0), 0); + + clk = clk_reg_prcc_pclk("p6_pclk1", "per6clk", clkrst6_base, + BIT(1), 0); + + clk = clk_reg_prcc_pclk("p6_pclk2", "per6clk", clkrst6_base, + BIT(2), 0); + + clk = clk_reg_prcc_pclk("p6_pclk3", "per6clk", clkrst6_base, + BIT(3), 0); + + clk = clk_reg_prcc_pclk("p6_pclk4", "per6clk", clkrst6_base, + BIT(4), 0); + + clk = clk_reg_prcc_pclk("p6_pclk5", "per6clk", clkrst6_base, + BIT(5), 0); + + clk = clk_reg_prcc_pclk("p6_pclk6", "per6clk", clkrst6_base, + BIT(6), 0); + + clk = clk_reg_prcc_pclk("p6_pclk7", "per6clk", clkrst6_base, + BIT(7), 0); + + /* PRCC K-clocks + * + * FIXME: Some drivers requires PERPIH[n| to be automatically enabled + * by enabling just the K-clock, even if it is not a valid parent to + * the K-clock. Until drivers get fixed we might need some kind of + * "parent muxed join". + */ + + /* Periph1 */ + clk = clk_reg_prcc_kclk("p1_uart0_kclk", "uartclk", + clkrst1_base, BIT(0), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p1_uart1_kclk", "uartclk", + clkrst1_base, BIT(1), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p1_i2c1_kclk", "i2cclk", + clkrst1_base, BIT(2), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p1_msp0_kclk", "msp02clk", + clkrst1_base, BIT(3), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p1_msp1_kclk", "msp1clk", + clkrst1_base, BIT(4), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p1_sdi0_kclk", "sdmmcclk", + clkrst1_base, BIT(5), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p1_i2c2_kclk", "i2cclk", + clkrst1_base, BIT(6), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p1_slimbus0_kclk", "slimclk", + clkrst1_base, BIT(8), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p1_i2c4_kclk", "i2cclk", + clkrst1_base, BIT(9), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p1_msp3_kclk", "msp1clk", + clkrst1_base, BIT(10), CLK_SET_RATE_GATE); + + /* Periph2 */ + clk = clk_reg_prcc_kclk("p2_i2c3_kclk", "i2cclk", + clkrst2_base, BIT(0), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p2_sdi4_kclk", "sdmmcclk", + clkrst2_base, BIT(2), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p2_msp2_kclk", "msp02clk", + clkrst2_base, BIT(3), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p2_sdi1_kclk", "sdmmcclk", + clkrst2_base, BIT(4), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p2_sdi3_kclk", "sdmmcclk", + clkrst2_base, BIT(5), CLK_SET_RATE_GATE); + + /* Note that rate is received from parent. */ + clk = clk_reg_prcc_kclk("p2_ssirx_kclk", "hsirxclk", + clkrst2_base, BIT(6), + CLK_SET_RATE_GATE|CLK_SET_RATE_PARENT); + clk = clk_reg_prcc_kclk("p2_ssitx_kclk", "hsitxclk", + clkrst2_base, BIT(7), + CLK_SET_RATE_GATE|CLK_SET_RATE_PARENT); + + /* Periph3 */ + clk = clk_reg_prcc_kclk("p3_ssp0_kclk", "sspclk", + clkrst3_base, BIT(1), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p3_ssp1_kclk", "sspclk", + clkrst3_base, BIT(2), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p3_i2c0_kclk", "i2cclk", + clkrst3_base, BIT(3), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p3_sdi2_kclk", "sdmmcclk", + clkrst3_base, BIT(4), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p3_ske_kclk", "rtc32k", + clkrst3_base, BIT(5), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p3_uart2_kclk", "uartclk", + clkrst3_base, BIT(6), CLK_SET_RATE_GATE); + + clk = clk_reg_prcc_kclk("p3_sdi5_kclk", "sdmmcclk", + clkrst3_base, BIT(7), CLK_SET_RATE_GATE); + + /* Periph6 */ + clk = clk_reg_prcc_kclk("p3_rng_kclk", "rngclk", + clkrst6_base, BIT(0), CLK_SET_RATE_GATE); +} diff --git a/include/linux/platform_data/clk-ux500.h b/include/linux/platform_data/clk-ux500.h index 9d98f3aaa16c..97baf831e071 100644 --- a/include/linux/platform_data/clk-ux500.h +++ b/include/linux/platform_data/clk-ux500.h @@ -10,6 +10,9 @@ #ifndef __CLK_UX500_H #define __CLK_UX500_H +void u8500_of_clk_init(u32 clkrst1_base, u32 clkrst2_base, u32 clkrst3_base, + u32 clkrst5_base, u32 clkrst6_base); + void u8500_clk_init(u32 clkrst1_base, u32 clkrst2_base, u32 clkrst3_base, u32 clkrst5_base, u32 clkrst6_base); void u9540_clk_init(u32 clkrst1_base, u32 clkrst2_base, u32 clkrst3_base, -- cgit v1.2.3 From ad93220de7da1f5bd8f9e4be1f1eeefba5eed0ad Mon Sep 17 00:00:00 2001 From: Dong Aisheng Date: Fri, 13 Sep 2013 19:11:35 +0800 Subject: mmc: sdhci-esdhc-imx: change pinctrl state according to uhs mode Without proper pinctrl state, the card may not be able to work on high speed stablely. e.g. SDR104. This patch add pinctrl state switch code according to different uhs mode include 100mhz sate, 200mhz sate and normal state (50Mhz and below). Signed-off-by: Dong Aisheng Acked-by: Shawn Guo Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-esdhc-imx.c | 96 ++++++++++++++++++++++++++++- include/linux/platform_data/mmc-esdhc-imx.h | 4 ++ 2 files changed, 99 insertions(+), 1 deletion(-) (limited to 'include/linux/platform_data') diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index f906c206901d..ed00d6ddd5fd 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -53,6 +53,10 @@ #define ESDHC_TUNING_BLOCK_PATTERN_LEN 64 +/* pinctrl state */ +#define ESDHC_PINCTRL_STATE_100MHZ "state_100mhz" +#define ESDHC_PINCTRL_STATE_200MHZ "state_200mhz" + /* * Our interpretation of the SDHCI_HOST_CONTROL register */ @@ -94,6 +98,9 @@ struct pltfm_imx_data { u32 scratchpad; enum imx_esdhc_type devtype; struct pinctrl *pinctrl; + struct pinctrl_state *pins_default; + struct pinctrl_state *pins_100mhz; + struct pinctrl_state *pins_200mhz; struct esdhc_platform_data boarddata; struct clk *clk_ipg; struct clk *clk_ahb; @@ -693,6 +700,62 @@ static int esdhc_executing_tuning(struct sdhci_host *host, u32 opcode) return ret; } +static int esdhc_change_pinstate(struct sdhci_host *host, + unsigned int uhs) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct pltfm_imx_data *imx_data = pltfm_host->priv; + struct pinctrl_state *pinctrl; + + dev_dbg(mmc_dev(host->mmc), "change pinctrl state for uhs %d\n", uhs); + + if (IS_ERR(imx_data->pinctrl) || + IS_ERR(imx_data->pins_default) || + IS_ERR(imx_data->pins_100mhz) || + IS_ERR(imx_data->pins_200mhz)) + return -EINVAL; + + switch (uhs) { + case MMC_TIMING_UHS_SDR50: + pinctrl = imx_data->pins_100mhz; + break; + case MMC_TIMING_UHS_SDR104: + pinctrl = imx_data->pins_200mhz; + break; + default: + /* back to default state for other legacy timing */ + pinctrl = imx_data->pins_default; + } + + return pinctrl_select_state(imx_data->pinctrl, pinctrl); +} + +static int esdhc_set_uhs_signaling(struct sdhci_host *host, unsigned int uhs) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct pltfm_imx_data *imx_data = pltfm_host->priv; + + switch (uhs) { + case MMC_TIMING_UHS_SDR12: + imx_data->uhs_mode = SDHCI_CTRL_UHS_SDR12; + break; + case MMC_TIMING_UHS_SDR25: + imx_data->uhs_mode = SDHCI_CTRL_UHS_SDR25; + break; + case MMC_TIMING_UHS_SDR50: + imx_data->uhs_mode = SDHCI_CTRL_UHS_SDR50; + break; + case MMC_TIMING_UHS_SDR104: + imx_data->uhs_mode = SDHCI_CTRL_UHS_SDR104; + break; + case MMC_TIMING_UHS_DDR50: + imx_data->uhs_mode = SDHCI_CTRL_UHS_DDR50; + break; + } + + return esdhc_change_pinstate(host, uhs); +} + static const struct sdhci_ops sdhci_esdhc_ops = { .read_l = esdhc_readl_le, .read_w = esdhc_readw_le, @@ -704,6 +767,7 @@ static const struct sdhci_ops sdhci_esdhc_ops = { .get_min_clock = esdhc_pltfm_get_min_clock, .get_ro = esdhc_pltfm_get_ro, .platform_bus_width = esdhc_pltfm_bus_width, + .set_uhs_signaling = esdhc_set_uhs_signaling, .platform_execute_tuning = esdhc_executing_tuning, }; @@ -746,6 +810,11 @@ sdhci_esdhc_imx_probe_dt(struct platform_device *pdev, of_property_read_u32(np, "max-frequency", &boarddata->f_max); + if (of_find_property(np, "no-1-8-v", NULL)) + boarddata->support_vsel = false; + else + boarddata->support_vsel = true; + return 0; } #else @@ -808,12 +877,20 @@ static int sdhci_esdhc_imx_probe(struct platform_device *pdev) clk_prepare_enable(imx_data->clk_ipg); clk_prepare_enable(imx_data->clk_ahb); - imx_data->pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + imx_data->pinctrl = devm_pinctrl_get(&pdev->dev); if (IS_ERR(imx_data->pinctrl)) { err = PTR_ERR(imx_data->pinctrl); goto disable_clk; } + imx_data->pins_default = pinctrl_lookup_state(imx_data->pinctrl, + PINCTRL_STATE_DEFAULT); + if (IS_ERR(imx_data->pins_default)) { + err = PTR_ERR(imx_data->pins_default); + dev_err(mmc_dev(host->mmc), "could not get default state\n"); + goto disable_clk; + } + host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; if (is_imx25_esdhc(imx_data) || is_imx35_esdhc(imx_data)) @@ -890,6 +967,23 @@ static int sdhci_esdhc_imx_probe(struct platform_device *pdev) break; } + /* sdr50 and sdr104 needs work on 1.8v signal voltage */ + if ((boarddata->support_vsel) && is_imx6q_usdhc(imx_data)) { + imx_data->pins_100mhz = pinctrl_lookup_state(imx_data->pinctrl, + ESDHC_PINCTRL_STATE_100MHZ); + imx_data->pins_200mhz = pinctrl_lookup_state(imx_data->pinctrl, + ESDHC_PINCTRL_STATE_200MHZ); + if (IS_ERR(imx_data->pins_100mhz) || + IS_ERR(imx_data->pins_200mhz)) { + dev_warn(mmc_dev(host->mmc), + "could not get ultra high speed state, work on normal mode\n"); + /* fall back to not support uhs by specify no 1.8v quirk */ + host->quirks2 |= SDHCI_QUIRK2_NO_1_8_V; + } + } else { + host->quirks2 |= SDHCI_QUIRK2_NO_1_8_V; + } + err = sdhci_add_host(host); if (err) goto disable_clk; diff --git a/include/linux/platform_data/mmc-esdhc-imx.h b/include/linux/platform_data/mmc-esdhc-imx.h index d44912d81578..a0f5a8f9b3bc 100644 --- a/include/linux/platform_data/mmc-esdhc-imx.h +++ b/include/linux/platform_data/mmc-esdhc-imx.h @@ -10,6 +10,8 @@ #ifndef __ASM_ARCH_IMX_ESDHC_H #define __ASM_ARCH_IMX_ESDHC_H +#include + enum wp_types { ESDHC_WP_NONE, /* no WP, neither controller nor gpio */ ESDHC_WP_CONTROLLER, /* mmc controller internal WP */ @@ -32,6 +34,7 @@ enum cd_types { * @cd_gpio: gpio for card_detect interrupt * @wp_type: type of write_protect method (see wp_types enum above) * @cd_type: type of card_detect method (see cd_types enum above) + * @support_vsel: indicate it supports 1.8v switching */ struct esdhc_platform_data { @@ -41,5 +44,6 @@ struct esdhc_platform_data { enum cd_types cd_type; int max_bus_width; unsigned int f_max; + bool support_vsel; }; #endif /* __ASM_ARCH_IMX_ESDHC_H */ -- cgit v1.2.3 From 74227e65f9742f559f6e243ba2c9a983e1f1221d Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Tue, 17 Sep 2013 00:32:34 +0200 Subject: clk: nomadik: declare OF clock provider Common clock framework allows to register clock providers to get called on of_clk_init() by using CLK_OF_DECLARE. This converts nomadik clock provider to make use of it and get rid of the mach specific clk init call. As clocks require system reset controller base address to be initialized each clock driver checks src_base and calls new nomadik_src_init if required. Signed-off-by: Sebastian Hesselbarth Acked-by: Linus Walleij Acked-by: Mike Turquette --- arch/arm/mach-nomadik/cpu-8815.c | 5 ++-- drivers/clk/clk-nomadik.c | 45 +++++++++++-------------------- include/linux/platform_data/clk-nomadik.h | 2 -- 3 files changed, 17 insertions(+), 35 deletions(-) delete mode 100644 include/linux/platform_data/clk-nomadik.h (limited to 'include/linux/platform_data') diff --git a/arch/arm/mach-nomadik/cpu-8815.c b/arch/arm/mach-nomadik/cpu-8815.c index 0fcb14966d5a..2be38a00a3d9 100644 --- a/arch/arm/mach-nomadik/cpu-8815.c +++ b/arch/arm/mach-nomadik/cpu-8815.c @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include #include @@ -115,8 +115,7 @@ static void cpu8815_restart(enum reboot_mode mode, const char *cmd) static void __init cpu8815_timer_init_of(void) { - /* We need this to be up now */ - nomadik_clk_init(); + of_clk_init(NULL); clocksource_of_init(); } diff --git a/drivers/clk/clk-nomadik.c b/drivers/clk/clk-nomadik.c index 06b29c264dbe..ce2d6b32e3f5 100644 --- a/drivers/clk/clk-nomadik.c +++ b/drivers/clk/clk-nomadik.c @@ -512,6 +512,9 @@ static void __init of_nomadik_pll_setup(struct device_node *np) const char *parent_name; u32 pll_id; + if (!src_base) + nomadik_src_init(); + if (of_property_read_u32(np, "pll-id", &pll_id)) { pr_err("%s: PLL \"%s\" missing pll-id property\n", __func__, clk_name); @@ -522,6 +525,8 @@ static void __init of_nomadik_pll_setup(struct device_node *np) if (!IS_ERR(clk)) of_clk_add_provider(np, of_clk_src_simple_get, clk); } +CLK_OF_DECLARE(nomadik_pll_clk, + "st,nomadik-pll-clock", of_nomadik_pll_setup); static void __init of_nomadik_hclk_setup(struct device_node *np) { @@ -529,6 +534,9 @@ static void __init of_nomadik_hclk_setup(struct device_node *np) const char *clk_name = np->name; const char *parent_name; + if (!src_base) + nomadik_src_init(); + parent_name = of_clk_get_parent_name(np, 0); /* * The HCLK divides PLL1 with 1 (passthru), 2, 3 or 4. @@ -541,6 +549,8 @@ static void __init of_nomadik_hclk_setup(struct device_node *np) if (!IS_ERR(clk)) of_clk_add_provider(np, of_clk_src_simple_get, clk); } +CLK_OF_DECLARE(nomadik_hclk_clk, + "st,nomadik-hclk-clock", of_nomadik_hclk_setup); static void __init of_nomadik_src_clk_setup(struct device_node *np) { @@ -549,6 +559,9 @@ static void __init of_nomadik_src_clk_setup(struct device_node *np) const char *parent_name; u32 clk_id; + if (!src_base) + nomadik_src_init(); + if (of_property_read_u32(np, "clock-id", &clk_id)) { pr_err("%s: SRC clock \"%s\" missing clock-id property\n", __func__, clk_name); @@ -559,33 +572,5 @@ static void __init of_nomadik_src_clk_setup(struct device_node *np) if (!IS_ERR(clk)) of_clk_add_provider(np, of_clk_src_simple_get, clk); } - -static const struct of_device_id nomadik_src_clk_match[] __initconst = { - { - .compatible = "fixed-clock", - .data = of_fixed_clk_setup, - }, - { - .compatible = "fixed-factor-clock", - .data = of_fixed_factor_clk_setup, - }, - { - .compatible = "st,nomadik-pll-clock", - .data = of_nomadik_pll_setup, - }, - { - .compatible = "st,nomadik-hclk-clock", - .data = of_nomadik_hclk_setup, - }, - { - .compatible = "st,nomadik-src-clock", - .data = of_nomadik_src_clk_setup, - }, - { /* sentinel */ } -}; - -void __init nomadik_clk_init(void) -{ - nomadik_src_init(); - of_clk_init(nomadik_src_clk_match); -} +CLK_OF_DECLARE(nomadik_src_clk, + "st,nomadik-src-clock", of_nomadik_src_clk_setup); diff --git a/include/linux/platform_data/clk-nomadik.h b/include/linux/platform_data/clk-nomadik.h deleted file mode 100644 index 5713c87b2477..000000000000 --- a/include/linux/platform_data/clk-nomadik.h +++ /dev/null @@ -1,2 +0,0 @@ -/* Minimal platform data header */ -void nomadik_clk_init(void); -- cgit v1.2.3 From 25f73ed5c67d17ecf8cefd560f55211cce726086 Mon Sep 17 00:00:00 2001 From: Vivien Didelot Date: Fri, 27 Sep 2013 15:06:28 -0400 Subject: misc: (at24) move header to linux/platform_data/ This patch moves the at24.h header from include/linux/i2c to include/linux/platform_data and updates existing support accordingly. It also fixes the following checkpatch warning: WARNING: please, no space before tabs #436: FILE: include/linux/platform_data/at24.h:31: + * ^Iu8 *mac_addr = ethernet_pdata->mac_addr;$ Signed-off-by: Vivien Didelot Signed-off-by: Wolfram Sang --- MAINTAINERS | 2 +- arch/arm/mach-at91/board-sam9260ek.c | 2 +- arch/arm/mach-at91/board-sam9263ek.c | 2 +- arch/arm/mach-davinci/board-da830-evm.c | 2 +- arch/arm/mach-davinci/board-da850-evm.c | 2 +- arch/arm/mach-davinci/board-dm365-evm.c | 2 +- arch/arm/mach-davinci/board-dm644x-evm.c | 2 +- arch/arm/mach-davinci/board-dm646x-evm.c | 2 +- arch/arm/mach-davinci/board-mityomapl138.c | 2 +- arch/arm/mach-davinci/board-sffsdr.c | 2 +- arch/arm/mach-imx/mach-pca100.c | 2 +- arch/arm/mach-imx/mach-pcm037.c | 2 +- arch/arm/mach-imx/mach-pcm038.c | 2 +- arch/arm/mach-imx/mach-pcm043.c | 2 +- arch/arm/mach-imx/mach-vpr200.c | 2 +- arch/arm/mach-kirkwood/lacie_v2-common.c | 2 +- arch/arm/mach-omap1/board-osk.c | 2 +- arch/arm/mach-omap2/board-cm-t35.c | 2 +- arch/arm/mach-omap2/board-h4.c | 2 +- arch/arm/mach-omap2/board-omap3stalker.c | 2 +- arch/arm/mach-pxa/stargate2.c | 2 +- arch/arm/mach-s3c24xx/mach-mini2440.c | 2 +- drivers/misc/eeprom/at24.c | 2 +- include/linux/i2c/at24.h | 55 ------------------------------ include/linux/platform_data/at24.h | 55 ++++++++++++++++++++++++++++++ 25 files changed, 78 insertions(+), 78 deletions(-) delete mode 100644 include/linux/i2c/at24.h create mode 100644 include/linux/platform_data/at24.h (limited to 'include/linux/platform_data') diff --git a/MAINTAINERS b/MAINTAINERS index 284969fa2896..b9d19a7f3144 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1396,7 +1396,7 @@ M: Wolfram Sang L: linux-i2c@vger.kernel.org S: Maintained F: drivers/misc/eeprom/at24.c -F: include/linux/i2c/at24.h +F: include/linux/platform_data/at24.h ATA OVER ETHERNET (AOE) DRIVER M: "Ed L. Cashin" diff --git a/arch/arm/mach-at91/board-sam9260ek.c b/arch/arm/mach-at91/board-sam9260ek.c index 0b153c87521d..f4f8735315da 100644 --- a/arch/arm/mach-at91/board-sam9260ek.c +++ b/arch/arm/mach-at91/board-sam9260ek.c @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include diff --git a/arch/arm/mach-at91/board-sam9263ek.c b/arch/arm/mach-at91/board-sam9263ek.c index 3284df05df14..947e134ac4c3 100644 --- a/arch/arm/mach-at91/board-sam9263ek.c +++ b/arch/arm/mach-at91/board-sam9263ek.c @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-davinci/board-da830-evm.c b/arch/arm/mach-davinci/board-da830-evm.c index c4bdc0a1c36e..66b5b3cb5376 100644 --- a/arch/arm/mach-davinci/board-da830-evm.c +++ b/arch/arm/mach-davinci/board-da830-evm.c @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-davinci/board-da850-evm.c b/arch/arm/mach-davinci/board-da850-evm.c index dd1fb24521aa..f25a569b0009 100644 --- a/arch/arm/mach-davinci/board-da850-evm.c +++ b/arch/arm/mach-davinci/board-da850-evm.c @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-davinci/board-dm365-evm.c b/arch/arm/mach-davinci/board-dm365-evm.c index 92b7f770615a..30ee81fac925 100644 --- a/arch/arm/mach-davinci/board-dm365-evm.c +++ b/arch/arm/mach-davinci/board-dm365-evm.c @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-davinci/board-dm644x-evm.c b/arch/arm/mach-davinci/board-dm644x-evm.c index 40bb9b5b87e8..f21fde9dce00 100644 --- a/arch/arm/mach-davinci/board-dm644x-evm.c +++ b/arch/arm/mach-davinci/board-dm644x-evm.c @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-davinci/board-dm646x-evm.c b/arch/arm/mach-davinci/board-dm646x-evm.c index 2bc3651d56cc..db2df32da6a8 100644 --- a/arch/arm/mach-davinci/board-dm646x-evm.c +++ b/arch/arm/mach-davinci/board-dm646x-evm.c @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include diff --git a/arch/arm/mach-davinci/board-mityomapl138.c b/arch/arm/mach-davinci/board-mityomapl138.c index cd0f58730c2b..7aa105b1fd0f 100644 --- a/arch/arm/mach-davinci/board-mityomapl138.c +++ b/arch/arm/mach-davinci/board-mityomapl138.c @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-davinci/board-sffsdr.c b/arch/arm/mach-davinci/board-sffsdr.c index d84360148100..41c7c9615791 100644 --- a/arch/arm/mach-davinci/board-sffsdr.c +++ b/arch/arm/mach-davinci/board-sffsdr.c @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-imx/mach-pca100.c b/arch/arm/mach-imx/mach-pca100.c index 19bb6441a7d4..c5f95674e9b7 100644 --- a/arch/arm/mach-imx/mach-pca100.c +++ b/arch/arm/mach-imx/mach-pca100.c @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-imx/mach-pcm037.c b/arch/arm/mach-imx/mach-pcm037.c index bc0261e99d39..20cc53f4cee1 100644 --- a/arch/arm/mach-imx/mach-pcm037.c +++ b/arch/arm/mach-imx/mach-pcm037.c @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-imx/mach-pcm038.c b/arch/arm/mach-imx/mach-pcm038.c index e805ac273e9c..592ddbe031ac 100644 --- a/arch/arm/mach-imx/mach-pcm038.c +++ b/arch/arm/mach-imx/mach-pcm038.c @@ -18,7 +18,7 @@ */ #include -#include +#include #include #include #include diff --git a/arch/arm/mach-imx/mach-pcm043.c b/arch/arm/mach-imx/mach-pcm043.c index b726cb1c5fdd..ac504b67326b 100644 --- a/arch/arm/mach-imx/mach-pcm043.c +++ b/arch/arm/mach-imx/mach-pcm043.c @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include diff --git a/arch/arm/mach-imx/mach-vpr200.c b/arch/arm/mach-imx/mach-vpr200.c index 0910761e8280..8825d1217d18 100644 --- a/arch/arm/mach-imx/mach-vpr200.c +++ b/arch/arm/mach-imx/mach-vpr200.c @@ -29,7 +29,7 @@ #include #include -#include +#include #include #include "common.h" diff --git a/arch/arm/mach-kirkwood/lacie_v2-common.c b/arch/arm/mach-kirkwood/lacie_v2-common.c index 489495976fcd..8e3e4331c380 100644 --- a/arch/arm/mach-kirkwood/lacie_v2-common.c +++ b/arch/arm/mach-kirkwood/lacie_v2-common.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-omap1/board-osk.c b/arch/arm/mach-omap1/board-osk.c index a7ce69286688..d68909b095f1 100644 --- a/arch/arm/mach-omap1/board-osk.c +++ b/arch/arm/mach-omap1/board-osk.c @@ -300,7 +300,7 @@ static struct omap_lcd_config osk_lcd_config __initdata = { #ifdef CONFIG_OMAP_OSK_MISTRAL #include -#include +#include #include #include diff --git a/arch/arm/mach-omap2/board-cm-t35.c b/arch/arm/mach-omap2/board-cm-t35.c index 33d159e2386e..8dd0ec858cf1 100644 --- a/arch/arm/mach-omap2/board-cm-t35.c +++ b/arch/arm/mach-omap2/board-cm-t35.c @@ -25,7 +25,7 @@ #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-omap2/board-h4.c b/arch/arm/mach-omap2/board-h4.c index 87e41a8b8d46..f7808349a734 100644 --- a/arch/arm/mach-omap2/board-h4.c +++ b/arch/arm/mach-omap2/board-h4.c @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-omap2/board-omap3stalker.c b/arch/arm/mach-omap2/board-omap3stalker.c index ba8342fef799..119efaf5808a 100644 --- a/arch/arm/mach-omap2/board-omap3stalker.c +++ b/arch/arm/mach-omap2/board-omap3stalker.c @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include diff --git a/arch/arm/mach-pxa/stargate2.c b/arch/arm/mach-pxa/stargate2.c index 62aea3e835f3..01de542432a6 100644 --- a/arch/arm/mach-pxa/stargate2.c +++ b/arch/arm/mach-pxa/stargate2.c @@ -27,7 +27,7 @@ #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-s3c24xx/mach-mini2440.c b/arch/arm/mach-s3c24xx/mach-mini2440.c index a83db46320bc..4a18d49a63e0 100644 --- a/arch/arm/mach-s3c24xx/mach-mini2440.c +++ b/arch/arm/mach-s3c24xx/mach-mini2440.c @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c index 5d4fd69d04ca..4ef01ab67853 100644 --- a/drivers/misc/eeprom/at24.c +++ b/drivers/misc/eeprom/at24.c @@ -22,7 +22,7 @@ #include #include #include -#include +#include /* * I2C EEPROMs from most vendors are inexpensive and mostly interchangeable. diff --git a/include/linux/i2c/at24.h b/include/linux/i2c/at24.h deleted file mode 100644 index 285025a9cdc9..000000000000 --- a/include/linux/i2c/at24.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * at24.h - platform_data for the at24 (generic eeprom) driver - * (C) Copyright 2008 by Pengutronix - * (C) Copyright 2012 by Wolfram Sang - * same license as the driver - */ - -#ifndef _LINUX_AT24_H -#define _LINUX_AT24_H - -#include -#include - -/** - * struct at24_platform_data - data to set up at24 (generic eeprom) driver - * @byte_len: size of eeprom in byte - * @page_size: number of byte which can be written in one go - * @flags: tunable options, check AT24_FLAG_* defines - * @setup: an optional callback invoked after eeprom is probed; enables kernel - code to access eeprom via memory_accessor, see example - * @context: optional parameter passed to setup() - * - * If you set up a custom eeprom type, please double-check the parameters. - * Especially page_size needs extra care, as you risk data loss if your value - * is bigger than what the chip actually supports! - * - * An example in pseudo code for a setup() callback: - * - * void get_mac_addr(struct memory_accessor *mem_acc, void *context) - * { - * u8 *mac_addr = ethernet_pdata->mac_addr; - * off_t offset = context; - * - * // Read MAC addr from EEPROM - * if (mem_acc->read(mem_acc, mac_addr, offset, ETH_ALEN) == ETH_ALEN) - * pr_info("Read MAC addr from EEPROM: %pM\n", mac_addr); - * } - * - * This function pointer and context can now be set up in at24_platform_data. - */ - -struct at24_platform_data { - u32 byte_len; /* size (sum of all addr) */ - u16 page_size; /* for writes */ - u8 flags; -#define AT24_FLAG_ADDR16 0x80 /* address pointer is 16 bit */ -#define AT24_FLAG_READONLY 0x40 /* sysfs-entry will be read-only */ -#define AT24_FLAG_IRUGO 0x20 /* sysfs-entry will be world-readable */ -#define AT24_FLAG_TAKE8ADDR 0x10 /* take always 8 addresses (24c00) */ - - void (*setup)(struct memory_accessor *, void *context); - void *context; -}; - -#endif /* _LINUX_AT24_H */ diff --git a/include/linux/platform_data/at24.h b/include/linux/platform_data/at24.h new file mode 100644 index 000000000000..c42aa89d34ee --- /dev/null +++ b/include/linux/platform_data/at24.h @@ -0,0 +1,55 @@ +/* + * at24.h - platform_data for the at24 (generic eeprom) driver + * (C) Copyright 2008 by Pengutronix + * (C) Copyright 2012 by Wolfram Sang + * same license as the driver + */ + +#ifndef _LINUX_AT24_H +#define _LINUX_AT24_H + +#include +#include + +/** + * struct at24_platform_data - data to set up at24 (generic eeprom) driver + * @byte_len: size of eeprom in byte + * @page_size: number of byte which can be written in one go + * @flags: tunable options, check AT24_FLAG_* defines + * @setup: an optional callback invoked after eeprom is probed; enables kernel + code to access eeprom via memory_accessor, see example + * @context: optional parameter passed to setup() + * + * If you set up a custom eeprom type, please double-check the parameters. + * Especially page_size needs extra care, as you risk data loss if your value + * is bigger than what the chip actually supports! + * + * An example in pseudo code for a setup() callback: + * + * void get_mac_addr(struct memory_accessor *mem_acc, void *context) + * { + * u8 *mac_addr = ethernet_pdata->mac_addr; + * off_t offset = context; + * + * // Read MAC addr from EEPROM + * if (mem_acc->read(mem_acc, mac_addr, offset, ETH_ALEN) == ETH_ALEN) + * pr_info("Read MAC addr from EEPROM: %pM\n", mac_addr); + * } + * + * This function pointer and context can now be set up in at24_platform_data. + */ + +struct at24_platform_data { + u32 byte_len; /* size (sum of all addr) */ + u16 page_size; /* for writes */ + u8 flags; +#define AT24_FLAG_ADDR16 0x80 /* address pointer is 16 bit */ +#define AT24_FLAG_READONLY 0x40 /* sysfs-entry will be read-only */ +#define AT24_FLAG_IRUGO 0x20 /* sysfs-entry will be world-readable */ +#define AT24_FLAG_TAKE8ADDR 0x10 /* take always 8 addresses (24c00) */ + + void (*setup)(struct memory_accessor *, void *context); + void *context; +}; + +#endif /* _LINUX_AT24_H */ -- cgit v1.2.3 From ddeccb8d6b5bbe2c1e3a29f8c74b52f170c2207d Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Tue, 8 Oct 2013 06:42:10 +0900 Subject: dmaengine: add driver for Samsung s3c24xx SoCs This adds a new driver to support the s3c24xx dma using the dmaengine and makes the old one in mach-s3c24xx obsolete in the long run. Conceptually the s3c24xx-dma feels like a distant relative of the pl08x with numerous virtual channels being mapped to a lot less physical ones. The driver therefore borrows a lot from the amba-pl08x driver in this regard. Functionality-wise the driver gains a memcpy ability in addition to the slave_sg one. The driver supports both the method for requesting the peripheral used by SoCs before the S3C2443 and the different method for S3C2443 and later. On earlier SoCs the hardware channels usable for specific peripherals is constrainted while on later SoCs all channels can be used for any peripheral. Tested on a s3c2416-based board, memcpy using the dmatest module and slave_sg partially using the spi-s3c64xx driver. Signed-off-by: Heiko Stuebner Acked-by: Linus Walleij Acked-by: Vinod Koul Signed-off-by: Kukjin Kim --- drivers/dma/Kconfig | 12 + drivers/dma/Makefile | 1 + drivers/dma/s3c24xx-dma.c | 1340 +++++++++++++++++++++++++++++ include/linux/platform_data/dma-s3c24xx.h | 43 + 4 files changed, 1396 insertions(+) create mode 100644 drivers/dma/s3c24xx-dma.c create mode 100644 include/linux/platform_data/dma-s3c24xx.h (limited to 'include/linux/platform_data') diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 526ec77c7ba0..d639115aa875 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -154,6 +154,18 @@ config TEGRA20_APB_DMA This DMA controller transfers data from memory to peripheral fifo or vice versa. It does not support memory to memory data transfer. +config S3C24XX_DMAC + tristate "Samsung S3C24XX DMA support" + depends on ARCH_S3C24XX && !S3C24XX_DMA + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + help + Support for the Samsung S3C24XX DMA controller driver. The + DMA controller is having multiple DMA channels which can be + configured for different peripherals like audio, UART, SPI. + The DMA controller can transfer data from memory to peripheral, + periphal to memory, periphal to periphal and memory to memory. + source "drivers/dma/sh/Kconfig" config COH901318 diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index db89035b3626..0ce2da97e429 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_SIRF_DMA) += sirf-dma.o obj-$(CONFIG_TI_EDMA) += edma.o obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o obj-$(CONFIG_TEGRA20_APB_DMA) += tegra20-apb-dma.o +obj-$(CONFIG_S3C24XX_DMAC) += s3c24xx-dma.o obj-$(CONFIG_PL330_DMA) += pl330.o obj-$(CONFIG_PCH_DMA) += pch_dma.o obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o diff --git a/drivers/dma/s3c24xx-dma.c b/drivers/dma/s3c24xx-dma.c new file mode 100644 index 000000000000..56c92538bc4c --- /dev/null +++ b/drivers/dma/s3c24xx-dma.c @@ -0,0 +1,1340 @@ +/* + * S3C24XX DMA handling + * + * Copyright (c) 2013 Heiko Stuebner + * + * based on amba-pl08x.c + * + * Copyright (c) 2006 ARM Ltd. + * Copyright (c) 2010 ST-Ericsson SA + * + * Author: Peter Pearse + * Author: Linus Walleij + * + * 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. + * + * The DMA controllers in S3C24XX SoCs have a varying number of DMA signals + * that can be routed to any of the 4 to 8 hardware-channels. + * + * Therefore on these DMA controllers the number of channels + * and the number of incoming DMA signals are two totally different things. + * It is usually not possible to theoretically handle all physical signals, + * so a multiplexing scheme with possible denial of use is necessary. + * + * Open items: + * - bursts + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dmaengine.h" +#include "virt-dma.h" + +#define MAX_DMA_CHANNELS 8 + +#define S3C24XX_DISRC 0x00 +#define S3C24XX_DISRCC 0x04 +#define S3C24XX_DISRCC_INC_INCREMENT 0 +#define S3C24XX_DISRCC_INC_FIXED BIT(0) +#define S3C24XX_DISRCC_LOC_AHB 0 +#define S3C24XX_DISRCC_LOC_APB BIT(1) + +#define S3C24XX_DIDST 0x08 +#define S3C24XX_DIDSTC 0x0c +#define S3C24XX_DIDSTC_INC_INCREMENT 0 +#define S3C24XX_DIDSTC_INC_FIXED BIT(0) +#define S3C24XX_DIDSTC_LOC_AHB 0 +#define S3C24XX_DIDSTC_LOC_APB BIT(1) +#define S3C24XX_DIDSTC_INT_TC0 0 +#define S3C24XX_DIDSTC_INT_RELOAD BIT(2) + +#define S3C24XX_DCON 0x10 + +#define S3C24XX_DCON_TC_MASK 0xfffff +#define S3C24XX_DCON_DSZ_BYTE (0 << 20) +#define S3C24XX_DCON_DSZ_HALFWORD (1 << 20) +#define S3C24XX_DCON_DSZ_WORD (2 << 20) +#define S3C24XX_DCON_DSZ_MASK (3 << 20) +#define S3C24XX_DCON_DSZ_SHIFT 20 +#define S3C24XX_DCON_AUTORELOAD 0 +#define S3C24XX_DCON_NORELOAD BIT(22) +#define S3C24XX_DCON_HWTRIG BIT(23) +#define S3C24XX_DCON_HWSRC_SHIFT 24 +#define S3C24XX_DCON_SERV_SINGLE 0 +#define S3C24XX_DCON_SERV_WHOLE BIT(27) +#define S3C24XX_DCON_TSZ_UNIT 0 +#define S3C24XX_DCON_TSZ_BURST4 BIT(28) +#define S3C24XX_DCON_INT BIT(29) +#define S3C24XX_DCON_SYNC_PCLK 0 +#define S3C24XX_DCON_SYNC_HCLK BIT(30) +#define S3C24XX_DCON_DEMAND 0 +#define S3C24XX_DCON_HANDSHAKE BIT(31) + +#define S3C24XX_DSTAT 0x14 +#define S3C24XX_DSTAT_STAT_BUSY BIT(20) +#define S3C24XX_DSTAT_CURRTC_MASK 0xfffff + +#define S3C24XX_DMASKTRIG 0x20 +#define S3C24XX_DMASKTRIG_SWTRIG BIT(0) +#define S3C24XX_DMASKTRIG_ON BIT(1) +#define S3C24XX_DMASKTRIG_STOP BIT(2) + +#define S3C24XX_DMAREQSEL 0x24 +#define S3C24XX_DMAREQSEL_HW BIT(0) + +/* + * S3C2410, S3C2440 and S3C2442 SoCs cannot select any physical channel + * for a DMA source. Instead only specific channels are valid. + * All of these SoCs have 4 physical channels and the number of request + * source bits is 3. Additionally we also need 1 bit to mark the channel + * as valid. + * Therefore we separate the chansel element of the channel data into 4 + * parts of 4 bits each, to hold the information if the channel is valid + * and the hw request source to use. + * + * Example: + * SDI is valid on channels 0, 2 and 3 - with varying hw request sources. + * For it the chansel field would look like + * + * ((BIT(3) | 1) << 3 * 4) | // channel 3, with request source 1 + * ((BIT(3) | 2) << 2 * 4) | // channel 2, with request source 2 + * ((BIT(3) | 2) << 0 * 4) // channel 0, with request source 2 + */ +#define S3C24XX_CHANSEL_WIDTH 4 +#define S3C24XX_CHANSEL_VALID BIT(3) +#define S3C24XX_CHANSEL_REQ_MASK 7 + +/* + * struct soc_data - vendor-specific config parameters for individual SoCs + * @stride: spacing between the registers of each channel + * @has_reqsel: does the controller use the newer requestselection mechanism + * @has_clocks: are controllable dma-clocks present + */ +struct soc_data { + int stride; + bool has_reqsel; + bool has_clocks; +}; + +/* + * enum s3c24xx_dma_chan_state - holds the virtual channel states + * @S3C24XX_DMA_CHAN_IDLE: the channel is idle + * @S3C24XX_DMA_CHAN_RUNNING: the channel has allocated a physical transport + * channel and is running a transfer on it + * @S3C24XX_DMA_CHAN_WAITING: the channel is waiting for a physical transport + * channel to become available (only pertains to memcpy channels) + */ +enum s3c24xx_dma_chan_state { + S3C24XX_DMA_CHAN_IDLE, + S3C24XX_DMA_CHAN_RUNNING, + S3C24XX_DMA_CHAN_WAITING, +}; + +/* + * struct s3c24xx_sg - structure containing data per sg + * @src_addr: src address of sg + * @dst_addr: dst address of sg + * @len: transfer len in bytes + * @node: node for txd's dsg_list + */ +struct s3c24xx_sg { + dma_addr_t src_addr; + dma_addr_t dst_addr; + size_t len; + struct list_head node; +}; + +/* + * struct s3c24xx_txd - wrapper for struct dma_async_tx_descriptor + * @vd: virtual DMA descriptor + * @dsg_list: list of children sg's + * @at: sg currently being transfered + * @width: transfer width + * @disrcc: value for source control register + * @didstc: value for destination control register + * @dcon: base value for dcon register + */ +struct s3c24xx_txd { + struct virt_dma_desc vd; + struct list_head dsg_list; + struct list_head *at; + u8 width; + u32 disrcc; + u32 didstc; + u32 dcon; +}; + +struct s3c24xx_dma_chan; + +/* + * struct s3c24xx_dma_phy - holder for the physical channels + * @id: physical index to this channel + * @valid: does the channel have all required elements + * @base: virtual memory base (remapped) for the this channel + * @irq: interrupt for this channel + * @clk: clock for this channel + * @lock: a lock to use when altering an instance of this struct + * @serving: virtual channel currently being served by this physicalchannel + * @host: a pointer to the host (internal use) + */ +struct s3c24xx_dma_phy { + unsigned int id; + bool valid; + void __iomem *base; + unsigned int irq; + struct clk *clk; + spinlock_t lock; + struct s3c24xx_dma_chan *serving; + struct s3c24xx_dma_engine *host; +}; + +/* + * struct s3c24xx_dma_chan - this structure wraps a DMA ENGINE channel + * @id: the id of the channel + * @name: name of the channel + * @vc: wrappped virtual channel + * @phy: the physical channel utilized by this channel, if there is one + * @runtime_addr: address for RX/TX according to the runtime config + * @at: active transaction on this channel + * @lock: a lock for this channel data + * @host: a pointer to the host (internal use) + * @state: whether the channel is idle, running etc + * @slave: whether this channel is a device (slave) or for memcpy + */ +struct s3c24xx_dma_chan { + int id; + const char *name; + struct virt_dma_chan vc; + struct s3c24xx_dma_phy *phy; + struct dma_slave_config cfg; + struct s3c24xx_txd *at; + struct s3c24xx_dma_engine *host; + enum s3c24xx_dma_chan_state state; + bool slave; +}; + +/* + * struct s3c24xx_dma_engine - the local state holder for the S3C24XX + * @pdev: the corresponding platform device + * @pdata: platform data passed in from the platform/machine + * @base: virtual memory base (remapped) + * @slave: slave engine for this instance + * @memcpy: memcpy engine for this instance + * @phy_chans: array of data for the physical channels + */ +struct s3c24xx_dma_engine { + struct platform_device *pdev; + const struct s3c24xx_dma_platdata *pdata; + struct soc_data *sdata; + void __iomem *base; + struct dma_device slave; + struct dma_device memcpy; + struct s3c24xx_dma_phy *phy_chans; +}; + +/* + * Physical channel handling + */ + +/* + * Check whether a certain channel is busy or not. + */ +static int s3c24xx_dma_phy_busy(struct s3c24xx_dma_phy *phy) +{ + unsigned int val = readl(phy->base + S3C24XX_DSTAT); + return val & S3C24XX_DSTAT_STAT_BUSY; +} + +static bool s3c24xx_dma_phy_valid(struct s3c24xx_dma_chan *s3cchan, + struct s3c24xx_dma_phy *phy) +{ + struct s3c24xx_dma_engine *s3cdma = s3cchan->host; + const struct s3c24xx_dma_platdata *pdata = s3cdma->pdata; + struct s3c24xx_dma_channel *cdata = &pdata->channels[s3cchan->id]; + int phyvalid; + + /* every phy is valid for memcopy channels */ + if (!s3cchan->slave) + return true; + + /* On newer variants all phys can be used for all virtual channels */ + if (s3cdma->sdata->has_reqsel) + return true; + + phyvalid = (cdata->chansel >> (phy->id * S3C24XX_CHANSEL_WIDTH)); + return (phyvalid & S3C24XX_CHANSEL_VALID) ? true : false; +} + +/* + * Allocate a physical channel for a virtual channel + * + * Try to locate a physical channel to be used for this transfer. If all + * are taken return NULL and the requester will have to cope by using + * some fallback PIO mode or retrying later. + */ +static +struct s3c24xx_dma_phy *s3c24xx_dma_get_phy(struct s3c24xx_dma_chan *s3cchan) +{ + struct s3c24xx_dma_engine *s3cdma = s3cchan->host; + const struct s3c24xx_dma_platdata *pdata = s3cdma->pdata; + struct s3c24xx_dma_channel *cdata; + struct s3c24xx_dma_phy *phy = NULL; + unsigned long flags; + int i; + int ret; + + if (s3cchan->slave) + cdata = &pdata->channels[s3cchan->id]; + + for (i = 0; i < s3cdma->pdata->num_phy_channels; i++) { + phy = &s3cdma->phy_chans[i]; + + if (!phy->valid) + continue; + + if (!s3c24xx_dma_phy_valid(s3cchan, phy)) + continue; + + spin_lock_irqsave(&phy->lock, flags); + + if (!phy->serving) { + phy->serving = s3cchan; + spin_unlock_irqrestore(&phy->lock, flags); + break; + } + + spin_unlock_irqrestore(&phy->lock, flags); + } + + /* No physical channel available, cope with it */ + if (i == s3cdma->pdata->num_phy_channels) { + dev_warn(&s3cdma->pdev->dev, "no phy channel available\n"); + return NULL; + } + + /* start the phy clock */ + if (s3cdma->sdata->has_clocks) { + ret = clk_enable(phy->clk); + if (ret) { + dev_err(&s3cdma->pdev->dev, "could not enable clock for channel %d, err %d\n", + phy->id, ret); + phy->serving = NULL; + return NULL; + } + } + + return phy; +} + +/* + * Mark the physical channel as free. + * + * This drops the link between the physical and virtual channel. + */ +static inline void s3c24xx_dma_put_phy(struct s3c24xx_dma_phy *phy) +{ + struct s3c24xx_dma_engine *s3cdma = phy->host; + + if (s3cdma->sdata->has_clocks) + clk_disable(phy->clk); + + phy->serving = NULL; +} + +/* + * Stops the channel by writing the stop bit. + * This should not be used for an on-going transfer, but as a method of + * shutting down a channel (eg, when it's no longer used) or terminating a + * transfer. + */ +static void s3c24xx_dma_terminate_phy(struct s3c24xx_dma_phy *phy) +{ + writel(S3C24XX_DMASKTRIG_STOP, phy->base + S3C24XX_DMASKTRIG); +} + +/* + * Virtual channel handling + */ + +static inline +struct s3c24xx_dma_chan *to_s3c24xx_dma_chan(struct dma_chan *chan) +{ + return container_of(chan, struct s3c24xx_dma_chan, vc.chan); +} + +static u32 s3c24xx_dma_getbytes_chan(struct s3c24xx_dma_chan *s3cchan) +{ + struct s3c24xx_dma_phy *phy = s3cchan->phy; + struct s3c24xx_txd *txd = s3cchan->at; + u32 tc = readl(phy->base + S3C24XX_DSTAT) & S3C24XX_DSTAT_CURRTC_MASK; + + return tc * txd->width; +} + +static int s3c24xx_dma_set_runtime_config(struct s3c24xx_dma_chan *s3cchan, + struct dma_slave_config *config) +{ + if (!s3cchan->slave) + return -EINVAL; + + /* Reject definitely invalid configurations */ + if (config->src_addr_width == DMA_SLAVE_BUSWIDTH_8_BYTES || + config->dst_addr_width == DMA_SLAVE_BUSWIDTH_8_BYTES) + return -EINVAL; + + s3cchan->cfg = *config; + + return 0; +} + +/* + * Transfer handling + */ + +static inline +struct s3c24xx_txd *to_s3c24xx_txd(struct dma_async_tx_descriptor *tx) +{ + return container_of(tx, struct s3c24xx_txd, vd.tx); +} + +static struct s3c24xx_txd *s3c24xx_dma_get_txd(void) +{ + struct s3c24xx_txd *txd = kzalloc(sizeof(*txd), GFP_NOWAIT); + + if (txd) { + INIT_LIST_HEAD(&txd->dsg_list); + txd->dcon = S3C24XX_DCON_INT | S3C24XX_DCON_NORELOAD; + } + + return txd; +} + +static void s3c24xx_dma_free_txd(struct s3c24xx_txd *txd) +{ + struct s3c24xx_sg *dsg, *_dsg; + + list_for_each_entry_safe(dsg, _dsg, &txd->dsg_list, node) { + list_del(&dsg->node); + kfree(dsg); + } + + kfree(txd); +} + +static void s3c24xx_dma_start_next_sg(struct s3c24xx_dma_chan *s3cchan, + struct s3c24xx_txd *txd) +{ + struct s3c24xx_dma_engine *s3cdma = s3cchan->host; + struct s3c24xx_dma_phy *phy = s3cchan->phy; + const struct s3c24xx_dma_platdata *pdata = s3cdma->pdata; + struct s3c24xx_sg *dsg = list_entry(txd->at, struct s3c24xx_sg, node); + u32 dcon = txd->dcon; + u32 val; + + /* transfer-size and -count from len and width */ + switch (txd->width) { + case 1: + dcon |= S3C24XX_DCON_DSZ_BYTE | dsg->len; + break; + case 2: + dcon |= S3C24XX_DCON_DSZ_HALFWORD | (dsg->len / 2); + break; + case 4: + dcon |= S3C24XX_DCON_DSZ_WORD | (dsg->len / 4); + break; + } + + if (s3cchan->slave) { + struct s3c24xx_dma_channel *cdata = + &pdata->channels[s3cchan->id]; + + if (s3cdma->sdata->has_reqsel) { + writel_relaxed((cdata->chansel << 1) | + S3C24XX_DMAREQSEL_HW, + phy->base + S3C24XX_DMAREQSEL); + } else { + int csel = cdata->chansel >> (phy->id * + S3C24XX_CHANSEL_WIDTH); + + csel &= S3C24XX_CHANSEL_REQ_MASK; + dcon |= csel << S3C24XX_DCON_HWSRC_SHIFT; + dcon |= S3C24XX_DCON_HWTRIG; + } + } else { + if (s3cdma->sdata->has_reqsel) + writel_relaxed(0, phy->base + S3C24XX_DMAREQSEL); + } + + writel_relaxed(dsg->src_addr, phy->base + S3C24XX_DISRC); + writel_relaxed(txd->disrcc, phy->base + S3C24XX_DISRCC); + writel_relaxed(dsg->dst_addr, phy->base + S3C24XX_DIDST); + writel_relaxed(txd->didstc, phy->base + S3C24XX_DIDSTC); + writel_relaxed(dcon, phy->base + S3C24XX_DCON); + + val = readl_relaxed(phy->base + S3C24XX_DMASKTRIG); + val &= ~S3C24XX_DMASKTRIG_STOP; + val |= S3C24XX_DMASKTRIG_ON; + + /* trigger the dma operation for memcpy transfers */ + if (!s3cchan->slave) + val |= S3C24XX_DMASKTRIG_SWTRIG; + + writel(val, phy->base + S3C24XX_DMASKTRIG); +} + +/* + * Set the initial DMA register values and start first sg. + */ +static void s3c24xx_dma_start_next_txd(struct s3c24xx_dma_chan *s3cchan) +{ + struct s3c24xx_dma_phy *phy = s3cchan->phy; + struct virt_dma_desc *vd = vchan_next_desc(&s3cchan->vc); + struct s3c24xx_txd *txd = to_s3c24xx_txd(&vd->tx); + + list_del(&txd->vd.node); + + s3cchan->at = txd; + + /* Wait for channel inactive */ + while (s3c24xx_dma_phy_busy(phy)) + cpu_relax(); + + /* point to the first element of the sg list */ + txd->at = txd->dsg_list.next; + s3c24xx_dma_start_next_sg(s3cchan, txd); +} + +static void s3c24xx_dma_free_txd_list(struct s3c24xx_dma_engine *s3cdma, + struct s3c24xx_dma_chan *s3cchan) +{ + LIST_HEAD(head); + + vchan_get_all_descriptors(&s3cchan->vc, &head); + vchan_dma_desc_free_list(&s3cchan->vc, &head); +} + +/* + * Try to allocate a physical channel. When successful, assign it to + * this virtual channel, and initiate the next descriptor. The + * virtual channel lock must be held at this point. + */ +static void s3c24xx_dma_phy_alloc_and_start(struct s3c24xx_dma_chan *s3cchan) +{ + struct s3c24xx_dma_engine *s3cdma = s3cchan->host; + struct s3c24xx_dma_phy *phy; + + phy = s3c24xx_dma_get_phy(s3cchan); + if (!phy) { + dev_dbg(&s3cdma->pdev->dev, "no physical channel available for xfer on %s\n", + s3cchan->name); + s3cchan->state = S3C24XX_DMA_CHAN_WAITING; + return; + } + + dev_dbg(&s3cdma->pdev->dev, "allocated physical channel %d for xfer on %s\n", + phy->id, s3cchan->name); + + s3cchan->phy = phy; + s3cchan->state = S3C24XX_DMA_CHAN_RUNNING; + + s3c24xx_dma_start_next_txd(s3cchan); +} + +static void s3c24xx_dma_phy_reassign_start(struct s3c24xx_dma_phy *phy, + struct s3c24xx_dma_chan *s3cchan) +{ + struct s3c24xx_dma_engine *s3cdma = s3cchan->host; + + dev_dbg(&s3cdma->pdev->dev, "reassigned physical channel %d for xfer on %s\n", + phy->id, s3cchan->name); + + /* + * We do this without taking the lock; we're really only concerned + * about whether this pointer is NULL or not, and we're guaranteed + * that this will only be called when it _already_ is non-NULL. + */ + phy->serving = s3cchan; + s3cchan->phy = phy; + s3cchan->state = S3C24XX_DMA_CHAN_RUNNING; + s3c24xx_dma_start_next_txd(s3cchan); +} + +/* + * Free a physical DMA channel, potentially reallocating it to another + * virtual channel if we have any pending. + */ +static void s3c24xx_dma_phy_free(struct s3c24xx_dma_chan *s3cchan) +{ + struct s3c24xx_dma_engine *s3cdma = s3cchan->host; + struct s3c24xx_dma_chan *p, *next; + +retry: + next = NULL; + + /* Find a waiting virtual channel for the next transfer. */ + list_for_each_entry(p, &s3cdma->memcpy.channels, vc.chan.device_node) + if (p->state == S3C24XX_DMA_CHAN_WAITING) { + next = p; + break; + } + + if (!next) { + list_for_each_entry(p, &s3cdma->slave.channels, + vc.chan.device_node) + if (p->state == S3C24XX_DMA_CHAN_WAITING && + s3c24xx_dma_phy_valid(p, s3cchan->phy)) { + next = p; + break; + } + } + + /* Ensure that the physical channel is stopped */ + s3c24xx_dma_terminate_phy(s3cchan->phy); + + if (next) { + bool success; + + /* + * Eww. We know this isn't going to deadlock + * but lockdep probably doesn't. + */ + spin_lock(&next->vc.lock); + /* Re-check the state now that we have the lock */ + success = next->state == S3C24XX_DMA_CHAN_WAITING; + if (success) + s3c24xx_dma_phy_reassign_start(s3cchan->phy, next); + spin_unlock(&next->vc.lock); + + /* If the state changed, try to find another channel */ + if (!success) + goto retry; + } else { + /* No more jobs, so free up the physical channel */ + s3c24xx_dma_put_phy(s3cchan->phy); + } + + s3cchan->phy = NULL; + s3cchan->state = S3C24XX_DMA_CHAN_IDLE; +} + +static void s3c24xx_dma_unmap_buffers(struct s3c24xx_txd *txd) +{ + struct device *dev = txd->vd.tx.chan->device->dev; + struct s3c24xx_sg *dsg; + + if (!(txd->vd.tx.flags & DMA_COMPL_SKIP_SRC_UNMAP)) { + if (txd->vd.tx.flags & DMA_COMPL_SRC_UNMAP_SINGLE) + list_for_each_entry(dsg, &txd->dsg_list, node) + dma_unmap_single(dev, dsg->src_addr, dsg->len, + DMA_TO_DEVICE); + else { + list_for_each_entry(dsg, &txd->dsg_list, node) + dma_unmap_page(dev, dsg->src_addr, dsg->len, + DMA_TO_DEVICE); + } + } + + if (!(txd->vd.tx.flags & DMA_COMPL_SKIP_DEST_UNMAP)) { + if (txd->vd.tx.flags & DMA_COMPL_DEST_UNMAP_SINGLE) + list_for_each_entry(dsg, &txd->dsg_list, node) + dma_unmap_single(dev, dsg->dst_addr, dsg->len, + DMA_FROM_DEVICE); + else + list_for_each_entry(dsg, &txd->dsg_list, node) + dma_unmap_page(dev, dsg->dst_addr, dsg->len, + DMA_FROM_DEVICE); + } +} + +static void s3c24xx_dma_desc_free(struct virt_dma_desc *vd) +{ + struct s3c24xx_txd *txd = to_s3c24xx_txd(&vd->tx); + struct s3c24xx_dma_chan *s3cchan = to_s3c24xx_dma_chan(vd->tx.chan); + + if (!s3cchan->slave) + s3c24xx_dma_unmap_buffers(txd); + + s3c24xx_dma_free_txd(txd); +} + +static irqreturn_t s3c24xx_dma_irq(int irq, void *data) +{ + struct s3c24xx_dma_phy *phy = data; + struct s3c24xx_dma_chan *s3cchan = phy->serving; + struct s3c24xx_txd *txd; + + dev_dbg(&phy->host->pdev->dev, "interrupt on channel %d\n", phy->id); + + /* + * Interrupts happen to notify the completion of a transfer and the + * channel should have moved into its stop state already on its own. + * Therefore interrupts on channels not bound to a virtual channel + * should never happen. Nevertheless send a terminate command to the + * channel if the unlikely case happens. + */ + if (unlikely(!s3cchan)) { + dev_err(&phy->host->pdev->dev, "interrupt on unused channel %d\n", + phy->id); + + s3c24xx_dma_terminate_phy(phy); + + return IRQ_HANDLED; + } + + spin_lock(&s3cchan->vc.lock); + txd = s3cchan->at; + if (txd) { + /* when more sg's are in this txd, start the next one */ + if (!list_is_last(txd->at, &txd->dsg_list)) { + txd->at = txd->at->next; + s3c24xx_dma_start_next_sg(s3cchan, txd); + } else { + s3cchan->at = NULL; + vchan_cookie_complete(&txd->vd); + + /* + * And start the next descriptor (if any), + * otherwise free this channel. + */ + if (vchan_next_desc(&s3cchan->vc)) + s3c24xx_dma_start_next_txd(s3cchan); + else + s3c24xx_dma_phy_free(s3cchan); + } + } + spin_unlock(&s3cchan->vc.lock); + + return IRQ_HANDLED; +} + +/* + * The DMA ENGINE API + */ + +static int s3c24xx_dma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, + unsigned long arg) +{ + struct s3c24xx_dma_chan *s3cchan = to_s3c24xx_dma_chan(chan); + struct s3c24xx_dma_engine *s3cdma = s3cchan->host; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&s3cchan->vc.lock, flags); + + switch (cmd) { + case DMA_SLAVE_CONFIG: + ret = s3c24xx_dma_set_runtime_config(s3cchan, + (struct dma_slave_config *)arg); + break; + case DMA_TERMINATE_ALL: + if (!s3cchan->phy && !s3cchan->at) { + dev_err(&s3cdma->pdev->dev, "trying to terminate already stopped channel %d\n", + s3cchan->id); + ret = -EINVAL; + break; + } + + s3cchan->state = S3C24XX_DMA_CHAN_IDLE; + + /* Mark physical channel as free */ + if (s3cchan->phy) + s3c24xx_dma_phy_free(s3cchan); + + /* Dequeue current job */ + if (s3cchan->at) { + s3c24xx_dma_desc_free(&s3cchan->at->vd); + s3cchan->at = NULL; + } + + /* Dequeue jobs not yet fired as well */ + s3c24xx_dma_free_txd_list(s3cdma, s3cchan); + break; + default: + /* Unknown command */ + ret = -ENXIO; + break; + } + + spin_unlock_irqrestore(&s3cchan->vc.lock, flags); + + return ret; +} + +static int s3c24xx_dma_alloc_chan_resources(struct dma_chan *chan) +{ + return 0; +} + +static void s3c24xx_dma_free_chan_resources(struct dma_chan *chan) +{ + /* Ensure all queued descriptors are freed */ + vchan_free_chan_resources(to_virt_chan(chan)); +} + +static enum dma_status s3c24xx_dma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, struct dma_tx_state *txstate) +{ + struct s3c24xx_dma_chan *s3cchan = to_s3c24xx_dma_chan(chan); + struct s3c24xx_txd *txd; + struct s3c24xx_sg *dsg; + struct virt_dma_desc *vd; + unsigned long flags; + enum dma_status ret; + size_t bytes = 0; + + spin_lock_irqsave(&s3cchan->vc.lock, flags); + ret = dma_cookie_status(chan, cookie, txstate); + if (ret == DMA_SUCCESS) { + spin_unlock_irqrestore(&s3cchan->vc.lock, flags); + return ret; + } + + /* + * There's no point calculating the residue if there's + * no txstate to store the value. + */ + if (!txstate) { + spin_unlock_irqrestore(&s3cchan->vc.lock, flags); + return ret; + } + + vd = vchan_find_desc(&s3cchan->vc, cookie); + if (vd) { + /* On the issued list, so hasn't been processed yet */ + txd = to_s3c24xx_txd(&vd->tx); + + list_for_each_entry(dsg, &txd->dsg_list, node) + bytes += dsg->len; + } else { + /* + * Currently running, so sum over the pending sg's and + * the currently active one. + */ + txd = s3cchan->at; + + dsg = list_entry(txd->at, struct s3c24xx_sg, node); + list_for_each_entry_from(dsg, &txd->dsg_list, node) + bytes += dsg->len; + + bytes += s3c24xx_dma_getbytes_chan(s3cchan); + } + spin_unlock_irqrestore(&s3cchan->vc.lock, flags); + + /* + * This cookie not complete yet + * Get number of bytes left in the active transactions and queue + */ + dma_set_residue(txstate, bytes); + + /* Whether waiting or running, we're in progress */ + return ret; +} + +/* + * Initialize a descriptor to be used by memcpy submit + */ +static struct dma_async_tx_descriptor *s3c24xx_dma_prep_memcpy( + struct dma_chan *chan, dma_addr_t dest, dma_addr_t src, + size_t len, unsigned long flags) +{ + struct s3c24xx_dma_chan *s3cchan = to_s3c24xx_dma_chan(chan); + struct s3c24xx_dma_engine *s3cdma = s3cchan->host; + struct s3c24xx_txd *txd; + struct s3c24xx_sg *dsg; + int src_mod, dest_mod; + + dev_dbg(&s3cdma->pdev->dev, "prepare memcpy of %d bytes from %s\n", + len, s3cchan->name); + + if ((len & S3C24XX_DCON_TC_MASK) != len) { + dev_err(&s3cdma->pdev->dev, "memcpy size %d to large\n", len); + return NULL; + } + + txd = s3c24xx_dma_get_txd(); + if (!txd) + return NULL; + + dsg = kzalloc(sizeof(*dsg), GFP_NOWAIT); + if (!dsg) { + s3c24xx_dma_free_txd(txd); + return NULL; + } + list_add_tail(&dsg->node, &txd->dsg_list); + + dsg->src_addr = src; + dsg->dst_addr = dest; + dsg->len = len; + + /* + * Determine a suitable transfer width. + * The DMA controller cannot fetch/store information which is not + * naturally aligned on the bus, i.e., a 4 byte fetch must start at + * an address divisible by 4 - more generally addr % width must be 0. + */ + src_mod = src % 4; + dest_mod = dest % 4; + switch (len % 4) { + case 0: + txd->width = (src_mod == 0 && dest_mod == 0) ? 4 : 1; + break; + case 2: + txd->width = ((src_mod == 2 || src_mod == 0) && + (dest_mod == 2 || dest_mod == 0)) ? 2 : 1; + break; + default: + txd->width = 1; + break; + } + + txd->disrcc = S3C24XX_DISRCC_LOC_AHB | S3C24XX_DISRCC_INC_INCREMENT; + txd->didstc = S3C24XX_DIDSTC_LOC_AHB | S3C24XX_DIDSTC_INC_INCREMENT; + txd->dcon |= S3C24XX_DCON_DEMAND | S3C24XX_DCON_SYNC_HCLK | + S3C24XX_DCON_SERV_WHOLE; + + return vchan_tx_prep(&s3cchan->vc, &txd->vd, flags); +} + +static struct dma_async_tx_descriptor *s3c24xx_dma_prep_slave_sg( + struct dma_chan *chan, struct scatterlist *sgl, + unsigned int sg_len, enum dma_transfer_direction direction, + unsigned long flags, void *context) +{ + struct s3c24xx_dma_chan *s3cchan = to_s3c24xx_dma_chan(chan); + struct s3c24xx_dma_engine *s3cdma = s3cchan->host; + const struct s3c24xx_dma_platdata *pdata = s3cdma->pdata; + struct s3c24xx_dma_channel *cdata = &pdata->channels[s3cchan->id]; + struct s3c24xx_txd *txd; + struct s3c24xx_sg *dsg; + struct scatterlist *sg; + dma_addr_t slave_addr; + u32 hwcfg = 0; + int tmp; + + dev_dbg(&s3cdma->pdev->dev, "prepare transaction of %d bytes from %s\n", + sg_dma_len(sgl), s3cchan->name); + + txd = s3c24xx_dma_get_txd(); + if (!txd) + return NULL; + + if (cdata->handshake) + txd->dcon |= S3C24XX_DCON_HANDSHAKE; + + switch (cdata->bus) { + case S3C24XX_DMA_APB: + txd->dcon |= S3C24XX_DCON_SYNC_PCLK; + hwcfg |= S3C24XX_DISRCC_LOC_APB; + break; + case S3C24XX_DMA_AHB: + txd->dcon |= S3C24XX_DCON_SYNC_HCLK; + hwcfg |= S3C24XX_DISRCC_LOC_AHB; + break; + } + + /* + * Always assume our peripheral desintation is a fixed + * address in memory. + */ + hwcfg |= S3C24XX_DISRCC_INC_FIXED; + + /* + * Individual dma operations are requested by the slave, + * so serve only single atomic operations (S3C24XX_DCON_SERV_SINGLE). + */ + txd->dcon |= S3C24XX_DCON_SERV_SINGLE; + + if (direction == DMA_MEM_TO_DEV) { + txd->disrcc = S3C24XX_DISRCC_LOC_AHB | + S3C24XX_DISRCC_INC_INCREMENT; + txd->didstc = hwcfg; + slave_addr = s3cchan->cfg.dst_addr; + txd->width = s3cchan->cfg.dst_addr_width; + } else if (direction == DMA_DEV_TO_MEM) { + txd->disrcc = hwcfg; + txd->didstc = S3C24XX_DIDSTC_LOC_AHB | + S3C24XX_DIDSTC_INC_INCREMENT; + slave_addr = s3cchan->cfg.src_addr; + txd->width = s3cchan->cfg.src_addr_width; + } else { + s3c24xx_dma_free_txd(txd); + dev_err(&s3cdma->pdev->dev, + "direction %d unsupported\n", direction); + return NULL; + } + + for_each_sg(sgl, sg, sg_len, tmp) { + dsg = kzalloc(sizeof(*dsg), GFP_NOWAIT); + if (!dsg) { + s3c24xx_dma_free_txd(txd); + return NULL; + } + list_add_tail(&dsg->node, &txd->dsg_list); + + dsg->len = sg_dma_len(sg); + if (direction == DMA_MEM_TO_DEV) { + dsg->src_addr = sg_dma_address(sg); + dsg->dst_addr = slave_addr; + } else { /* DMA_DEV_TO_MEM */ + dsg->src_addr = slave_addr; + dsg->dst_addr = sg_dma_address(sg); + } + break; + } + + return vchan_tx_prep(&s3cchan->vc, &txd->vd, flags); +} + +/* + * Slave transactions callback to the slave device to allow + * synchronization of slave DMA signals with the DMAC enable + */ +static void s3c24xx_dma_issue_pending(struct dma_chan *chan) +{ + struct s3c24xx_dma_chan *s3cchan = to_s3c24xx_dma_chan(chan); + unsigned long flags; + + spin_lock_irqsave(&s3cchan->vc.lock, flags); + if (vchan_issue_pending(&s3cchan->vc)) { + if (!s3cchan->phy && s3cchan->state != S3C24XX_DMA_CHAN_WAITING) + s3c24xx_dma_phy_alloc_and_start(s3cchan); + } + spin_unlock_irqrestore(&s3cchan->vc.lock, flags); +} + +/* + * Bringup and teardown + */ + +/* + * Initialise the DMAC memcpy/slave channels. + * Make a local wrapper to hold required data + */ +static int s3c24xx_dma_init_virtual_channels(struct s3c24xx_dma_engine *s3cdma, + struct dma_device *dmadev, unsigned int channels, bool slave) +{ + struct s3c24xx_dma_chan *chan; + int i; + + INIT_LIST_HEAD(&dmadev->channels); + + /* + * Register as many many memcpy as we have physical channels, + * we won't always be able to use all but the code will have + * to cope with that situation. + */ + for (i = 0; i < channels; i++) { + chan = devm_kzalloc(dmadev->dev, sizeof(*chan), GFP_KERNEL); + if (!chan) { + dev_err(dmadev->dev, + "%s no memory for channel\n", __func__); + return -ENOMEM; + } + + chan->id = i; + chan->host = s3cdma; + chan->state = S3C24XX_DMA_CHAN_IDLE; + + if (slave) { + chan->slave = true; + chan->name = kasprintf(GFP_KERNEL, "slave%d", i); + if (!chan->name) + return -ENOMEM; + } else { + chan->name = kasprintf(GFP_KERNEL, "memcpy%d", i); + if (!chan->name) + return -ENOMEM; + } + dev_dbg(dmadev->dev, + "initialize virtual channel \"%s\"\n", + chan->name); + + chan->vc.desc_free = s3c24xx_dma_desc_free; + vchan_init(&chan->vc, dmadev); + } + dev_info(dmadev->dev, "initialized %d virtual %s channels\n", + i, slave ? "slave" : "memcpy"); + return i; +} + +static void s3c24xx_dma_free_virtual_channels(struct dma_device *dmadev) +{ + struct s3c24xx_dma_chan *chan = NULL; + struct s3c24xx_dma_chan *next; + + list_for_each_entry_safe(chan, + next, &dmadev->channels, vc.chan.device_node) + list_del(&chan->vc.chan.device_node); +} + +/* s3c2412 and s3c2413 have a 0x40 stride and dmareqsel mechanism */ +static struct soc_data soc_s3c2412 = { + .stride = 0x40, + .has_reqsel = true, + .has_clocks = true, +}; + +/* s3c2443 and following have a 0x100 stride and dmareqsel mechanism */ +static struct soc_data soc_s3c2443 = { + .stride = 0x100, + .has_reqsel = true, + .has_clocks = true, +}; + +static struct platform_device_id s3c24xx_dma_driver_ids[] = { + { + .name = "s3c2412-dma", + .driver_data = (kernel_ulong_t)&soc_s3c2412, + }, { + .name = "s3c2443-dma", + .driver_data = (kernel_ulong_t)&soc_s3c2443, + }, + { }, +}; + +static struct soc_data *s3c24xx_dma_get_soc_data(struct platform_device *pdev) +{ + return (struct soc_data *) + platform_get_device_id(pdev)->driver_data; +} + +static int s3c24xx_dma_probe(struct platform_device *pdev) +{ + const struct s3c24xx_dma_platdata *pdata = dev_get_platdata(&pdev->dev); + struct s3c24xx_dma_engine *s3cdma; + struct soc_data *sdata; + struct resource *res; + int ret; + int i; + + if (!pdata) { + dev_err(&pdev->dev, "platform data missing\n"); + return -ENODEV; + } + + /* Basic sanity check */ + if (pdata->num_phy_channels > MAX_DMA_CHANNELS) { + dev_err(&pdev->dev, "to many dma channels %d, max %d\n", + pdata->num_phy_channels, MAX_DMA_CHANNELS); + return -EINVAL; + } + + sdata = s3c24xx_dma_get_soc_data(pdev); + if (!sdata) + return -EINVAL; + + s3cdma = devm_kzalloc(&pdev->dev, sizeof(*s3cdma), GFP_KERNEL); + if (!s3cdma) + return -ENOMEM; + + s3cdma->pdev = pdev; + s3cdma->pdata = pdata; + s3cdma->sdata = sdata; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + s3cdma->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(s3cdma->base)) + return PTR_ERR(s3cdma->base); + + s3cdma->phy_chans = devm_kzalloc(&pdev->dev, + sizeof(struct s3c24xx_dma_phy) * + pdata->num_phy_channels, + GFP_KERNEL); + if (!s3cdma->phy_chans) + return -ENOMEM; + + /* aquire irqs and clocks for all physical channels */ + for (i = 0; i < pdata->num_phy_channels; i++) { + struct s3c24xx_dma_phy *phy = &s3cdma->phy_chans[i]; + char clk_name[6]; + + phy->id = i; + phy->base = s3cdma->base + (i * sdata->stride); + phy->host = s3cdma; + + phy->irq = platform_get_irq(pdev, i); + if (phy->irq < 0) { + dev_err(&pdev->dev, "failed to get irq %d, err %d\n", + i, phy->irq); + continue; + } + + ret = devm_request_irq(&pdev->dev, phy->irq, s3c24xx_dma_irq, + 0, pdev->name, phy); + if (ret) { + dev_err(&pdev->dev, "Unable to request irq for channel %d, error %d\n", + i, ret); + continue; + } + + if (sdata->has_clocks) { + sprintf(clk_name, "dma.%d", i); + phy->clk = devm_clk_get(&pdev->dev, clk_name); + if (IS_ERR(phy->clk) && sdata->has_clocks) { + dev_err(&pdev->dev, "unable to aquire clock for channel %d, error %lu", + i, PTR_ERR(phy->clk)); + continue; + } + + ret = clk_prepare(phy->clk); + if (ret) { + dev_err(&pdev->dev, "clock for phy %d failed, error %d\n", + i, ret); + continue; + } + } + + spin_lock_init(&phy->lock); + phy->valid = true; + + dev_dbg(&pdev->dev, "physical channel %d is %s\n", + i, s3c24xx_dma_phy_busy(phy) ? "BUSY" : "FREE"); + } + + /* Initialize memcpy engine */ + dma_cap_set(DMA_MEMCPY, s3cdma->memcpy.cap_mask); + dma_cap_set(DMA_PRIVATE, s3cdma->memcpy.cap_mask); + s3cdma->memcpy.dev = &pdev->dev; + s3cdma->memcpy.device_alloc_chan_resources = + s3c24xx_dma_alloc_chan_resources; + s3cdma->memcpy.device_free_chan_resources = + s3c24xx_dma_free_chan_resources; + s3cdma->memcpy.device_prep_dma_memcpy = s3c24xx_dma_prep_memcpy; + s3cdma->memcpy.device_tx_status = s3c24xx_dma_tx_status; + s3cdma->memcpy.device_issue_pending = s3c24xx_dma_issue_pending; + s3cdma->memcpy.device_control = s3c24xx_dma_control; + + /* Initialize slave engine for SoC internal dedicated peripherals */ + dma_cap_set(DMA_SLAVE, s3cdma->slave.cap_mask); + dma_cap_set(DMA_PRIVATE, s3cdma->slave.cap_mask); + s3cdma->slave.dev = &pdev->dev; + s3cdma->slave.device_alloc_chan_resources = + s3c24xx_dma_alloc_chan_resources; + s3cdma->slave.device_free_chan_resources = + s3c24xx_dma_free_chan_resources; + s3cdma->slave.device_tx_status = s3c24xx_dma_tx_status; + s3cdma->slave.device_issue_pending = s3c24xx_dma_issue_pending; + s3cdma->slave.device_prep_slave_sg = s3c24xx_dma_prep_slave_sg; + s3cdma->slave.device_control = s3c24xx_dma_control; + + /* Register as many memcpy channels as there are physical channels */ + ret = s3c24xx_dma_init_virtual_channels(s3cdma, &s3cdma->memcpy, + pdata->num_phy_channels, false); + if (ret <= 0) { + dev_warn(&pdev->dev, + "%s failed to enumerate memcpy channels - %d\n", + __func__, ret); + goto err_memcpy; + } + + /* Register slave channels */ + ret = s3c24xx_dma_init_virtual_channels(s3cdma, &s3cdma->slave, + pdata->num_channels, true); + if (ret <= 0) { + dev_warn(&pdev->dev, + "%s failed to enumerate slave channels - %d\n", + __func__, ret); + goto err_slave; + } + + ret = dma_async_device_register(&s3cdma->memcpy); + if (ret) { + dev_warn(&pdev->dev, + "%s failed to register memcpy as an async device - %d\n", + __func__, ret); + goto err_memcpy_reg; + } + + ret = dma_async_device_register(&s3cdma->slave); + if (ret) { + dev_warn(&pdev->dev, + "%s failed to register slave as an async device - %d\n", + __func__, ret); + goto err_slave_reg; + } + + platform_set_drvdata(pdev, s3cdma); + dev_info(&pdev->dev, "Loaded dma driver with %d physical channels\n", + pdata->num_phy_channels); + + return 0; + +err_slave_reg: + dma_async_device_unregister(&s3cdma->memcpy); +err_memcpy_reg: + s3c24xx_dma_free_virtual_channels(&s3cdma->slave); +err_slave: + s3c24xx_dma_free_virtual_channels(&s3cdma->memcpy); +err_memcpy: + if (sdata->has_clocks) + for (i = 0; i < pdata->num_phy_channels; i++) { + struct s3c24xx_dma_phy *phy = &s3cdma->phy_chans[i]; + if (phy->valid) + clk_unprepare(phy->clk); + } + + return ret; +} + +static int s3c24xx_dma_remove(struct platform_device *pdev) +{ + const struct s3c24xx_dma_platdata *pdata = dev_get_platdata(&pdev->dev); + struct s3c24xx_dma_engine *s3cdma = platform_get_drvdata(pdev); + struct soc_data *sdata = s3c24xx_dma_get_soc_data(pdev); + int i; + + dma_async_device_unregister(&s3cdma->slave); + dma_async_device_unregister(&s3cdma->memcpy); + + s3c24xx_dma_free_virtual_channels(&s3cdma->slave); + s3c24xx_dma_free_virtual_channels(&s3cdma->memcpy); + + if (sdata->has_clocks) + for (i = 0; i < pdata->num_phy_channels; i++) { + struct s3c24xx_dma_phy *phy = &s3cdma->phy_chans[i]; + if (phy->valid) + clk_unprepare(phy->clk); + } + + return 0; +} + +static struct platform_driver s3c24xx_dma_driver = { + .driver = { + .name = "s3c24xx-dma", + .owner = THIS_MODULE, + }, + .id_table = s3c24xx_dma_driver_ids, + .probe = s3c24xx_dma_probe, + .remove = s3c24xx_dma_remove, +}; + +module_platform_driver(s3c24xx_dma_driver); + +bool s3c24xx_dma_filter(struct dma_chan *chan, void *param) +{ + struct s3c24xx_dma_chan *s3cchan; + + if (chan->device->dev->driver != &s3c24xx_dma_driver.driver) + return false; + + s3cchan = to_s3c24xx_dma_chan(chan); + + return s3cchan->id == (int)param; +} +EXPORT_SYMBOL(s3c24xx_dma_filter); + +MODULE_DESCRIPTION("S3C24XX DMA Driver"); +MODULE_AUTHOR("Heiko Stuebner"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/platform_data/dma-s3c24xx.h b/include/linux/platform_data/dma-s3c24xx.h new file mode 100644 index 000000000000..5a0cfffe3bbb --- /dev/null +++ b/include/linux/platform_data/dma-s3c24xx.h @@ -0,0 +1,43 @@ +/* + * S3C24XX DMA handling + * + * Copyright (c) 2013 Heiko Stuebner + * + * 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. + */ + +enum s3c24xx_dma_bus { + S3C24XX_DMA_APB, + S3C24XX_DMA_AHB, +}; + +/** + * @bus: on which bus does the peripheral reside - AHB or APB. + * @handshake: is a handshake with the peripheral necessary + * @chansel: channel selection information, depending on variant; reqsel for + * s3c2443 and later and channel-selection map for earlier SoCs + * see CHANSEL doc in s3c2443-dma.c + */ +struct s3c24xx_dma_channel { + enum s3c24xx_dma_bus bus; + bool handshake; + u16 chansel; +}; + +/** + * struct s3c24xx_dma_platdata - platform specific settings + * @num_phy_channels: number of physical channels + * @channels: array of virtual channel descriptions + * @num_channels: number of virtual channels + */ +struct s3c24xx_dma_platdata { + int num_phy_channels; + struct s3c24xx_dma_channel *channels; + int num_channels; +}; + +struct dma_chan; +bool s3c24xx_dma_filter(struct dma_chan *chan, void *param); -- cgit v1.2.3 From 5578b266e9ae05391d53b446acf23818256ff13f Mon Sep 17 00:00:00 2001 From: Valentine Barshak Date: Thu, 10 Oct 2013 20:35:17 +0400 Subject: usb: phy: Add RCAR Gen2 USB phy This adds RCAR Gen2 USB phy support. The driver configures USB channels 0/2 which are shared between PCI USB hosts and USBHS/USBSS devices. It also controls internal USBHS phy. Signed-off-by: Valentine Barshak Signed-off-by: Felipe Balbi --- drivers/usb/phy/Kconfig | 13 ++ drivers/usb/phy/Makefile | 1 + drivers/usb/phy/phy-rcar-gen2-usb.c | 248 ++++++++++++++++++++++++ include/linux/platform_data/usb-rcar-gen2-phy.h | 22 +++ 4 files changed, 284 insertions(+) create mode 100644 drivers/usb/phy/phy-rcar-gen2-usb.c create mode 100644 include/linux/platform_data/usb-rcar-gen2-phy.h (limited to 'include/linux/platform_data') diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index d5589f9c60a9..c0c8cd37648e 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -214,6 +214,19 @@ config USB_RCAR_PHY To compile this driver as a module, choose M here: the module will be called phy-rcar-usb. +config USB_RCAR_GEN2_PHY + tristate "Renesas R-Car Gen2 USB PHY support" + depends on ARCH_R8A7790 || ARCH_R8A7791 || COMPILE_TEST + select USB_PHY + help + Say Y here to add support for the Renesas R-Car Gen2 USB PHY driver. + It is typically used to control internal USB PHY for USBHS, + and to configure shared USB channels 0 and 2. + This driver supports R8A7790 and R8A7791. + + To compile this driver as a module, choose M here: the + module will be called phy-rcar-gen2-usb. + config USB_ULPI bool "Generic ULPI Transceiver Driver" depends on ARM diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index 2135e85f46ed..8c5b14764b72 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -29,5 +29,6 @@ obj-$(CONFIG_USB_MSM_OTG) += phy-msm-usb.o obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o obj-$(CONFIG_USB_RCAR_PHY) += phy-rcar-usb.o +obj-$(CONFIG_USB_RCAR_GEN2_PHY) += phy-rcar-gen2-usb.o obj-$(CONFIG_USB_ULPI) += phy-ulpi.o obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o diff --git a/drivers/usb/phy/phy-rcar-gen2-usb.c b/drivers/usb/phy/phy-rcar-gen2-usb.c new file mode 100644 index 000000000000..a99a6953f11c --- /dev/null +++ b/drivers/usb/phy/phy-rcar-gen2-usb.c @@ -0,0 +1,248 @@ +/* + * Renesas R-Car Gen2 USB phy driver + * + * Copyright (C) 2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct rcar_gen2_usb_phy_priv { + struct usb_phy phy; + void __iomem *base; + struct clk *clk; + spinlock_t lock; + int usecount; + u32 ugctrl2; +}; + +#define usb_phy_to_priv(p) container_of(p, struct rcar_gen2_usb_phy_priv, phy) + +/* Low Power Status register */ +#define USBHS_LPSTS_REG 0x02 +#define USBHS_LPSTS_SUSPM (1 << 14) + +/* USB General control register */ +#define USBHS_UGCTRL_REG 0x80 +#define USBHS_UGCTRL_CONNECT (1 << 2) +#define USBHS_UGCTRL_PLLRESET (1 << 0) + +/* USB General control register 2 */ +#define USBHS_UGCTRL2_REG 0x84 +#define USBHS_UGCTRL2_USB0_PCI (1 << 4) +#define USBHS_UGCTRL2_USB0_HS (3 << 4) +#define USBHS_UGCTRL2_USB2_PCI (0 << 31) +#define USBHS_UGCTRL2_USB2_SS (1 << 31) + +/* USB General status register */ +#define USBHS_UGSTS_REG 0x88 +#define USBHS_UGSTS_LOCK (3 << 8) + +/* Enable USBHS internal phy */ +static int __rcar_gen2_usbhs_phy_enable(void __iomem *base) +{ + u32 val; + int i; + + /* USBHS PHY power on */ + val = ioread32(base + USBHS_UGCTRL_REG); + val &= ~USBHS_UGCTRL_PLLRESET; + iowrite32(val, base + USBHS_UGCTRL_REG); + + val = ioread16(base + USBHS_LPSTS_REG); + val |= USBHS_LPSTS_SUSPM; + iowrite16(val, base + USBHS_LPSTS_REG); + + for (i = 0; i < 20; i++) { + val = ioread32(base + USBHS_UGSTS_REG); + if ((val & USBHS_UGSTS_LOCK) == USBHS_UGSTS_LOCK) { + val = ioread32(base + USBHS_UGCTRL_REG); + val |= USBHS_UGCTRL_CONNECT; + iowrite32(val, base + USBHS_UGCTRL_REG); + return 0; + } + udelay(1); + } + + /* Timed out waiting for the PLL lock */ + return -ETIMEDOUT; +} + +/* Disable USBHS internal phy */ +static int __rcar_gen2_usbhs_phy_disable(void __iomem *base) +{ + u32 val; + + /* USBHS PHY power off */ + val = ioread32(base + USBHS_UGCTRL_REG); + val &= ~USBHS_UGCTRL_CONNECT; + iowrite32(val, base + USBHS_UGCTRL_REG); + + val = ioread16(base + USBHS_LPSTS_REG); + val &= ~USBHS_LPSTS_SUSPM; + iowrite16(val, base + USBHS_LPSTS_REG); + + val = ioread32(base + USBHS_UGCTRL_REG); + val |= USBHS_UGCTRL_PLLRESET; + iowrite32(val, base + USBHS_UGCTRL_REG); + return 0; +} + +/* Setup USB channels */ +static void __rcar_gen2_usb_phy_init(struct rcar_gen2_usb_phy_priv *priv) +{ + u32 val; + + clk_prepare_enable(priv->clk); + + /* Set USB channels in the USBHS UGCTRL2 register */ + val = ioread32(priv->base); + val &= ~(USBHS_UGCTRL2_USB0_HS | USBHS_UGCTRL2_USB2_SS); + val |= priv->ugctrl2; + iowrite32(val, priv->base); +} + +/* Shutdown USB channels */ +static void __rcar_gen2_usb_phy_shutdown(struct rcar_gen2_usb_phy_priv *priv) +{ + __rcar_gen2_usbhs_phy_disable(priv->base); + clk_disable_unprepare(priv->clk); +} + +static int rcar_gen2_usb_phy_set_suspend(struct usb_phy *phy, int suspend) +{ + struct rcar_gen2_usb_phy_priv *priv = usb_phy_to_priv(phy); + unsigned long flags; + int retval; + + spin_lock_irqsave(&priv->lock, flags); + retval = suspend ? __rcar_gen2_usbhs_phy_disable(priv->base) : + __rcar_gen2_usbhs_phy_enable(priv->base); + spin_unlock_irqrestore(&priv->lock, flags); + return retval; +} + +static int rcar_gen2_usb_phy_init(struct usb_phy *phy) +{ + struct rcar_gen2_usb_phy_priv *priv = usb_phy_to_priv(phy); + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + /* + * Enable the clock and setup USB channels + * if it's the first user + */ + if (!priv->usecount++) + __rcar_gen2_usb_phy_init(priv); + spin_unlock_irqrestore(&priv->lock, flags); + return 0; +} + +static void rcar_gen2_usb_phy_shutdown(struct usb_phy *phy) +{ + struct rcar_gen2_usb_phy_priv *priv = usb_phy_to_priv(phy); + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + if (!priv->usecount) { + dev_warn(phy->dev, "Trying to disable phy with 0 usecount\n"); + goto out; + } + + /* Disable everything if it's the last user */ + if (!--priv->usecount) + __rcar_gen2_usb_phy_shutdown(priv); +out: + spin_unlock_irqrestore(&priv->lock, flags); +} + +static int rcar_gen2_usb_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rcar_gen2_phy_platform_data *pdata; + struct rcar_gen2_usb_phy_priv *priv; + struct resource *res; + void __iomem *base; + struct clk *clk; + int retval; + + pdata = dev_get_platdata(&pdev->dev); + if (!pdata) { + dev_err(dev, "No platform data\n"); + return -EINVAL; + } + + clk = devm_clk_get(&pdev->dev, "usbhs"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Can't get the clock\n"); + return PTR_ERR(clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(dev, "Memory allocation failed\n"); + return -ENOMEM; + } + + spin_lock_init(&priv->lock); + priv->clk = clk; + priv->base = base; + priv->ugctrl2 = pdata->chan0_pci ? + USBHS_UGCTRL2_USB0_PCI : USBHS_UGCTRL2_USB0_HS; + priv->ugctrl2 |= pdata->chan2_pci ? + USBHS_UGCTRL2_USB2_PCI : USBHS_UGCTRL2_USB2_SS; + priv->phy.dev = dev; + priv->phy.label = dev_name(dev); + priv->phy.init = rcar_gen2_usb_phy_init; + priv->phy.shutdown = rcar_gen2_usb_phy_shutdown; + priv->phy.set_suspend = rcar_gen2_usb_phy_set_suspend; + + retval = usb_add_phy(&priv->phy, USB_PHY_TYPE_USB2); + if (retval < 0) { + dev_err(dev, "Failed to add USB phy\n"); + return retval; + } + + platform_set_drvdata(pdev, priv); + + return retval; +} + +static int rcar_gen2_usb_phy_remove(struct platform_device *pdev) +{ + struct rcar_gen2_usb_phy_priv *priv = platform_get_drvdata(pdev); + + usb_remove_phy(&priv->phy); + + return 0; +} + +static struct platform_driver rcar_gen2_usb_phy_driver = { + .driver = { + .name = "usb_phy_rcar_gen2", + }, + .probe = rcar_gen2_usb_phy_probe, + .remove = rcar_gen2_usb_phy_remove, +}; + +module_platform_driver(rcar_gen2_usb_phy_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas R-Car Gen2 USB phy"); +MODULE_AUTHOR("Valentine Barshak "); diff --git a/include/linux/platform_data/usb-rcar-gen2-phy.h b/include/linux/platform_data/usb-rcar-gen2-phy.h new file mode 100644 index 000000000000..dd3ba46c0d90 --- /dev/null +++ b/include/linux/platform_data/usb-rcar-gen2-phy.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, 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. + */ + +#ifndef __USB_RCAR_GEN2_PHY_H +#define __USB_RCAR_GEN2_PHY_H + +#include + +struct rcar_gen2_phy_platform_data { + /* USB channel 0 configuration */ + bool chan0_pci:1; /* true: PCI USB host 0, false: USBHS */ + /* USB channel 2 configuration */ + bool chan2_pci:1; /* true: PCI USB host 2, false: USBSS */ +}; + +#endif -- cgit v1.2.3 From dc7743aa3c49fabbc6dc9edbcf7df74d776ac32e Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Wed, 2 Oct 2013 21:39:40 -0700 Subject: pinctrl: single: Add support for auxdata For omaps, we still have dependencies to the legacy code for handling the PRM (Power Reset Management) interrupts, and also for reconfiguring the io wake-up chain after changes. Let's pass the PRM interrupt and the rearm functions via auxdata. Then when at some point we have a proper PRM driver, we can get the interrupt via device tree and set up the rearm function as exported function in the PRM driver. By using auxdata we can remove a dependency to the wake-up events for converting omap3 to be device tree only. Cc: Peter Ujfalusi Cc: Grygorii Strashko Cc: Prakash Manjunathappa Cc: Roger Quadros Cc: Haojian Zhuang Cc: linux-kernel@vger.kernel.org Reviewed-by: Kevin Hilman Tested-by: Kevin Hilman Acked-by: Linus Walleij Signed-off-by: Tony Lindgren --- drivers/pinctrl/pinctrl-single.c | 23 +++++++++++++++++++++++ include/linux/platform_data/pinctrl-single.h | 12 ++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 include/linux/platform_data/pinctrl-single.h (limited to 'include/linux/platform_data') diff --git a/drivers/pinctrl/pinctrl-single.c b/drivers/pinctrl/pinctrl-single.c index dad65df36f4b..c2aada71c915 100644 --- a/drivers/pinctrl/pinctrl-single.c +++ b/drivers/pinctrl/pinctrl-single.c @@ -28,6 +28,8 @@ #include #include +#include + #include "core.h" #include "pinconf.h" @@ -159,12 +161,14 @@ struct pcs_name { * @irq: optional interrupt for the controller * @irq_enable_mask: optional SoC specific interrupt enable mask * @irq_status_mask: optional SoC specific interrupt status mask + * @rearm: optional SoC specific wake-up rearm function */ struct pcs_soc_data { unsigned flags; int irq; unsigned irq_enable_mask; unsigned irq_status_mask; + void (*rearm)(void); }; /** @@ -1622,6 +1626,8 @@ static void pcs_irq_unmask(struct irq_data *d) struct pcs_soc_data *pcs_soc = irq_data_get_irq_chip_data(d); pcs_irq_set(pcs_soc, d->irq, true); + if (pcs_soc->rearm) + pcs_soc->rearm(); } /** @@ -1672,6 +1678,11 @@ static int pcs_irq_handle(struct pcs_soc_data *pcs_soc) } } + /* + * For debugging on omaps, you may want to call pcs_soc->rearm() + * here to see wake-up interrupts during runtime also. + */ + return count; } @@ -1835,6 +1846,7 @@ static int pcs_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; const struct of_device_id *match; + struct pcs_pdata *pdata; struct resource *res; struct pcs_device *pcs; const struct pcs_soc_data *soc; @@ -1949,6 +1961,17 @@ static int pcs_probe(struct platform_device *pdev) if (pcs->socdata.irq) pcs->flags |= PCS_FEAT_IRQ; + /* We still need auxdata for some omaps for PRM interrupts */ + pdata = dev_get_platdata(&pdev->dev); + if (pdata) { + if (pdata->rearm) + pcs->socdata.rearm = pdata->rearm; + if (pdata->irq) { + pcs->socdata.irq = pdata->irq; + pcs->flags |= PCS_FEAT_IRQ; + } + } + if (PCS_HAS_IRQ) { ret = pcs_irq_init_chained_handler(pcs, np); if (ret < 0) diff --git a/include/linux/platform_data/pinctrl-single.h b/include/linux/platform_data/pinctrl-single.h new file mode 100644 index 000000000000..72eacda9b360 --- /dev/null +++ b/include/linux/platform_data/pinctrl-single.h @@ -0,0 +1,12 @@ +/** + * irq: optional wake-up interrupt + * rearm: optional soc specific rearm function + * + * Note that the irq and rearm setup should come from device + * tree except for omap where there are still some dependencies + * to the legacy PRM code. + */ +struct pcs_pdata { + int irq; + void (*rearm)(void); +}; -- cgit v1.2.3 From 57ae1605c0f511bca212787d28d79a3f82c0a7f9 Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Thu, 10 Oct 2013 16:41:19 +0900 Subject: USB: ehci-s5p: Remove non-DT support The non-DT for EXYNOS SoCs is not supported from v3.11. Thus, there is no need to support non-DT for Exynos EHCI driver. The 'include/linux/platform_data/usb-ehci-s5p.h' file has been used for non-DT support. Thus, the 'usb-ehci-s5p.h' file can be removed. Signed-off-by: Jingoo Han Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-s5p.c | 32 ++++-------------------------- include/linux/platform_data/usb-ehci-s5p.h | 21 -------------------- 2 files changed, 4 insertions(+), 49 deletions(-) delete mode 100644 include/linux/platform_data/usb-ehci-s5p.h (limited to 'include/linux/platform_data') diff --git a/drivers/usb/host/ehci-s5p.c b/drivers/usb/host/ehci-s5p.c index 7c3de95c7054..45e1ad3f56b2 100644 --- a/drivers/usb/host/ehci-s5p.c +++ b/drivers/usb/host/ehci-s5p.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -47,11 +46,8 @@ struct s5p_ehci_hcd { struct clk *clk; struct usb_phy *phy; struct usb_otg *otg; - struct s5p_ehci_platdata *pdata; }; -static struct s5p_ehci_platdata empty_platdata; - #define to_s5p_ehci(hcd) (struct s5p_ehci_hcd *)(hcd_to_ehci(hcd)->priv) static void s5p_setup_vbus_gpio(struct platform_device *pdev) @@ -75,7 +71,6 @@ static void s5p_setup_vbus_gpio(struct platform_device *pdev) static int s5p_ehci_probe(struct platform_device *pdev) { - struct s5p_ehci_platdata *pdata = dev_get_platdata(&pdev->dev); struct s5p_ehci_hcd *s5p_ehci; struct usb_hcd *hcd; struct ehci_hcd *ehci; @@ -105,21 +100,14 @@ static int s5p_ehci_probe(struct platform_device *pdev) s5p_ehci = to_s5p_ehci(hcd); if (of_device_is_compatible(pdev->dev.of_node, - "samsung,exynos5440-ehci")) { - s5p_ehci->pdata = &empty_platdata; + "samsung,exynos5440-ehci")) goto skip_phy; - } phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); if (IS_ERR(phy)) { - /* Fallback to pdata */ - if (!pdata) { - usb_put_hcd(hcd); - dev_warn(&pdev->dev, "no platform data or transceiver defined\n"); - return -EPROBE_DEFER; - } else { - s5p_ehci->pdata = pdata; - } + usb_put_hcd(hcd); + dev_warn(&pdev->dev, "no platform data or transceiver defined\n"); + return -EPROBE_DEFER; } else { s5p_ehci->phy = phy; s5p_ehci->otg = phy->otg; @@ -167,8 +155,6 @@ skip_phy: if (s5p_ehci->phy) usb_phy_init(s5p_ehci->phy); - else if (s5p_ehci->pdata->phy_init) - s5p_ehci->pdata->phy_init(pdev, USB_PHY_TYPE_HOST); ehci = hcd_to_ehci(hcd); ehci->caps = hcd->regs; @@ -189,8 +175,6 @@ skip_phy: fail_add_hcd: if (s5p_ehci->phy) usb_phy_shutdown(s5p_ehci->phy); - else if (s5p_ehci->pdata->phy_exit) - s5p_ehci->pdata->phy_exit(pdev, USB_PHY_TYPE_HOST); fail_io: clk_disable_unprepare(s5p_ehci->clk); fail_clk: @@ -210,8 +194,6 @@ static int s5p_ehci_remove(struct platform_device *pdev) if (s5p_ehci->phy) usb_phy_shutdown(s5p_ehci->phy); - else if (s5p_ehci->pdata->phy_exit) - s5p_ehci->pdata->phy_exit(pdev, USB_PHY_TYPE_HOST); clk_disable_unprepare(s5p_ehci->clk); @@ -225,7 +207,6 @@ static int s5p_ehci_suspend(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); struct s5p_ehci_hcd *s5p_ehci = to_s5p_ehci(hcd); - struct platform_device *pdev = to_platform_device(dev); bool do_wakeup = device_may_wakeup(dev); int rc; @@ -237,8 +218,6 @@ static int s5p_ehci_suspend(struct device *dev) if (s5p_ehci->phy) usb_phy_shutdown(s5p_ehci->phy); - else if (s5p_ehci->pdata->phy_exit) - s5p_ehci->pdata->phy_exit(pdev, USB_PHY_TYPE_HOST); clk_disable_unprepare(s5p_ehci->clk); @@ -249,7 +228,6 @@ static int s5p_ehci_resume(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); struct s5p_ehci_hcd *s5p_ehci = to_s5p_ehci(hcd); - struct platform_device *pdev = to_platform_device(dev); clk_prepare_enable(s5p_ehci->clk); @@ -258,8 +236,6 @@ static int s5p_ehci_resume(struct device *dev) if (s5p_ehci->phy) usb_phy_init(s5p_ehci->phy); - else if (s5p_ehci->pdata->phy_init) - s5p_ehci->pdata->phy_init(pdev, USB_PHY_TYPE_HOST); /* DMA burst Enable */ writel(EHCI_INSNREG00_ENABLE_DMA_BURST, EHCI_INSNREG00(hcd->regs)); diff --git a/include/linux/platform_data/usb-ehci-s5p.h b/include/linux/platform_data/usb-ehci-s5p.h deleted file mode 100644 index 5f28cae18582..000000000000 --- a/include/linux/platform_data/usb-ehci-s5p.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2011 Samsung Electronics Co.Ltd - * Author: Joonyoung Shim - * - * 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 __PLAT_SAMSUNG_EHCI_H -#define __PLAT_SAMSUNG_EHCI_H __FILE__ - -struct s5p_ehci_platdata { - int (*phy_init)(struct platform_device *pdev, int type); - int (*phy_exit)(struct platform_device *pdev, int type); -}; - -extern void s5p_ehci_set_platdata(struct s5p_ehci_platdata *pd); - -#endif /* __PLAT_SAMSUNG_EHCI_H */ -- cgit v1.2.3 From c00809d330cfe42469fcd1cfd63f0690b47ea9bb Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Mon, 14 Oct 2013 09:18:54 +0900 Subject: USB: ohci-exynos: Remove non-DT support The non-DT for EXYNOS SoCs is not supported from v3.11. Thus, there is no need to support non-DT for Exynos OHCI driver. The 'include/linux/platform_data/usb-ohci-exynos.h' file has been used for non-DT support. Thus, the 'usb-ohci-exynos.h' file can be removed. Signed-off-by: Jingoo Han Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-exynos.c | 18 +++--------------- include/linux/platform_data/usb-ohci-exynos.h | 21 --------------------- 2 files changed, 3 insertions(+), 36 deletions(-) delete mode 100644 include/linux/platform_data/usb-ohci-exynos.h (limited to 'include/linux/platform_data') diff --git a/drivers/usb/host/ohci-exynos.c b/drivers/usb/host/ohci-exynos.c index 17f46fbf047d..a87baedc0aa7 100644 --- a/drivers/usb/host/ohci-exynos.c +++ b/drivers/usb/host/ohci-exynos.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -38,7 +37,6 @@ struct exynos_ohci_hcd { struct clk *clk; struct usb_phy *phy; struct usb_otg *otg; - struct exynos4_ohci_platdata *pdata; }; static void exynos_ohci_phy_enable(struct platform_device *pdev) @@ -48,8 +46,6 @@ static void exynos_ohci_phy_enable(struct platform_device *pdev) if (exynos_ohci->phy) usb_phy_init(exynos_ohci->phy); - else if (exynos_ohci->pdata && exynos_ohci->pdata->phy_init) - exynos_ohci->pdata->phy_init(pdev, USB_PHY_TYPE_HOST); } static void exynos_ohci_phy_disable(struct platform_device *pdev) @@ -59,13 +55,10 @@ static void exynos_ohci_phy_disable(struct platform_device *pdev) if (exynos_ohci->phy) usb_phy_shutdown(exynos_ohci->phy); - else if (exynos_ohci->pdata && exynos_ohci->pdata->phy_exit) - exynos_ohci->pdata->phy_exit(pdev, USB_PHY_TYPE_HOST); } static int exynos_ohci_probe(struct platform_device *pdev) { - struct exynos4_ohci_platdata *pdata = dev_get_platdata(&pdev->dev); struct exynos_ohci_hcd *exynos_ohci; struct usb_hcd *hcd; struct resource *res; @@ -98,14 +91,9 @@ static int exynos_ohci_probe(struct platform_device *pdev) phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); if (IS_ERR(phy)) { - /* Fallback to pdata */ - if (!pdata) { - usb_put_hcd(hcd); - dev_warn(&pdev->dev, "no platform data or transceiver defined\n"); - return -EPROBE_DEFER; - } else { - exynos_ohci->pdata = pdata; - } + usb_put_hcd(hcd); + dev_warn(&pdev->dev, "no platform data or transceiver defined\n"); + return -EPROBE_DEFER; } else { exynos_ohci->phy = phy; exynos_ohci->otg = phy->otg; diff --git a/include/linux/platform_data/usb-ohci-exynos.h b/include/linux/platform_data/usb-ohci-exynos.h deleted file mode 100644 index c256c595be5e..000000000000 --- a/include/linux/platform_data/usb-ohci-exynos.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2011 Samsung Electronics Co.Ltd - * http://www.samsung.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. - */ - -#ifndef __MACH_EXYNOS_OHCI_H -#define __MACH_EXYNOS_OHCI_H - -struct exynos4_ohci_platdata { - int (*phy_init)(struct platform_device *pdev, int type); - int (*phy_exit)(struct platform_device *pdev, int type); -}; - -extern void exynos4_ohci_set_platdata(struct exynos4_ohci_platdata *pd); - -#endif /* __MACH_EXYNOS_OHCI_H */ -- cgit v1.2.3 From f1468a2077e8c00fddb6cecec41b356637195ca3 Mon Sep 17 00:00:00 2001 From: Sylwester Nawrocki Date: Wed, 16 Oct 2013 21:58:11 +0530 Subject: exynos4-is: Use the generic MIPI CSIS PHY driver Use the generic PHY API instead of the platform callback to control the MIPI CSIS DPHY. Signed-off-by: Sylwester Nawrocki Signed-off-by: Kyungmin Park Acked-by: Felipe Balbi Acked-by: Mauro Carvalho Chehab Signed-off-by: Kishon Vijay Abraham I Signed-off-by: Greg Kroah-Hartman --- drivers/media/platform/exynos4-is/Kconfig | 2 +- drivers/media/platform/exynos4-is/mipi-csis.c | 13 ++++++++++--- include/linux/platform_data/mipi-csis.h | 9 --------- 3 files changed, 11 insertions(+), 13 deletions(-) (limited to 'include/linux/platform_data') diff --git a/drivers/media/platform/exynos4-is/Kconfig b/drivers/media/platform/exynos4-is/Kconfig index 53ad0f080179..d2d3b4b61435 100644 --- a/drivers/media/platform/exynos4-is/Kconfig +++ b/drivers/media/platform/exynos4-is/Kconfig @@ -29,7 +29,7 @@ config VIDEO_S5P_FIMC config VIDEO_S5P_MIPI_CSIS tristate "S5P/EXYNOS MIPI-CSI2 receiver (MIPI-CSIS) driver" depends on REGULATOR - select S5P_SETUP_MIPIPHY + select GENERIC_PHY help This is a V4L2 driver for Samsung S5P and EXYNOS4 SoC MIPI-CSI2 receiver (MIPI-CSIS) devices. diff --git a/drivers/media/platform/exynos4-is/mipi-csis.c b/drivers/media/platform/exynos4-is/mipi-csis.c index 0914230b42de..9fc2af6a0446 100644 --- a/drivers/media/platform/exynos4-is/mipi-csis.c +++ b/drivers/media/platform/exynos4-is/mipi-csis.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -180,6 +181,7 @@ struct csis_drvdata { * @sd: v4l2_subdev associated with CSIS device instance * @index: the hardware instance index * @pdev: CSIS platform device + * @phy: pointer to the CSIS generic PHY * @regs: mmaped I/O registers memory * @supplies: CSIS regulator supplies * @clock: CSIS clocks @@ -203,6 +205,7 @@ struct csis_state { struct v4l2_subdev sd; u8 index; struct platform_device *pdev; + struct phy *phy; void __iomem *regs; struct regulator_bulk_data supplies[CSIS_NUM_SUPPLIES]; struct clk *clock[NUM_CSIS_CLOCKS]; @@ -779,8 +782,8 @@ static int s5pcsis_parse_dt(struct platform_device *pdev, "samsung,csis-wclk"); state->num_lanes = endpoint.bus.mipi_csi2.num_data_lanes; - of_node_put(node); + return 0; } #else @@ -829,6 +832,10 @@ static int s5pcsis_probe(struct platform_device *pdev) return -EINVAL; } + state->phy = devm_phy_get(dev, "csis"); + if (IS_ERR(state->phy)) + return PTR_ERR(state->phy); + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); state->regs = devm_ioremap_resource(dev, mem_res); if (IS_ERR(state->regs)) @@ -922,7 +929,7 @@ static int s5pcsis_pm_suspend(struct device *dev, bool runtime) mutex_lock(&state->lock); if (state->flags & ST_POWERED) { s5pcsis_stop_stream(state); - ret = s5p_csis_phy_enable(state->index, false); + ret = phy_power_off(state->phy); if (ret) goto unlock; ret = regulator_bulk_disable(CSIS_NUM_SUPPLIES, @@ -958,7 +965,7 @@ static int s5pcsis_pm_resume(struct device *dev, bool runtime) state->supplies); if (ret) goto unlock; - ret = s5p_csis_phy_enable(state->index, true); + ret = phy_power_on(state->phy); if (!ret) { state->flags |= ST_POWERED; } else { diff --git a/include/linux/platform_data/mipi-csis.h b/include/linux/platform_data/mipi-csis.h index bf34e17cee7f..c2fd9024717c 100644 --- a/include/linux/platform_data/mipi-csis.h +++ b/include/linux/platform_data/mipi-csis.h @@ -25,13 +25,4 @@ struct s5p_platform_mipi_csis { u8 hs_settle; }; -/** - * s5p_csis_phy_enable - global MIPI-CSI receiver D-PHY control - * @id: MIPI-CSIS harware instance index (0...1) - * @on: true to enable D-PHY and deassert its reset - * false to disable D-PHY - * @return: 0 on success, or negative error code on failure - */ -int s5p_csis_phy_enable(int id, bool on); - #endif /* __PLAT_SAMSUNG_MIPI_CSIS_H_ */ -- cgit v1.2.3 From 1fecf8958eb7f90791f2c7e99afac393b64fa976 Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Mon, 21 Oct 2013 05:32:48 +0900 Subject: ARM: S3C24XX: add dma pdata for s3c2410, s3c2440 and s3c2442 s3c2410 and s3c2442 share the same dma channels while s3c2440 has slight differences. But on all three the reachable sources per dma channel has constraints attached and thus encodes the usable combinations using the S3C24XX_DMA_CHANREQ macro. This also fixes the warning about s3c2410_dma_resource being unused as reported by Olof Johansson. Reported-by: Olof Johansson Signed-off-by: Heiko Stuebner Reviewed-by: Tomasz Figa Signed-off-by: Kukjin Kim --- arch/arm/mach-s3c24xx/common.c | 100 ++++++++++++++++++++++++++++++ arch/arm/mach-s3c24xx/common.h | 2 + include/linux/platform_data/dma-s3c24xx.h | 3 + 3 files changed, 105 insertions(+) (limited to 'include/linux/platform_data') diff --git a/arch/arm/mach-s3c24xx/common.c b/arch/arm/mach-s3c24xx/common.c index bdcb29f4ea31..4adaa4b43ffe 100644 --- a/arch/arm/mach-s3c24xx/common.c +++ b/arch/arm/mach-s3c24xx/common.c @@ -343,6 +343,50 @@ static struct resource s3c2410_dma_resource[] = { }; #endif +#if defined(CONFIG_CPU_S3C2410) || defined(CONFIG_CPU_S3C2442) +static struct s3c24xx_dma_channel s3c2410_dma_channels[DMACH_MAX] = { + [DMACH_XD0] = { S3C24XX_DMA_AHB, true, S3C24XX_DMA_CHANREQ(0, 0), }, + [DMACH_XD1] = { S3C24XX_DMA_AHB, true, S3C24XX_DMA_CHANREQ(0, 1), }, + [DMACH_SDI] = { S3C24XX_DMA_APB, false, S3C24XX_DMA_CHANREQ(2, 0) | + S3C24XX_DMA_CHANREQ(2, 2) | + S3C24XX_DMA_CHANREQ(1, 3), + }, + [DMACH_SPI0] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(3, 1), }, + [DMACH_SPI1] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(2, 3), }, + [DMACH_UART0] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(1, 0), }, + [DMACH_UART1] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(1, 1), }, + [DMACH_UART2] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(0, 3), }, + [DMACH_TIMER] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(3, 0) | + S3C24XX_DMA_CHANREQ(3, 2) | + S3C24XX_DMA_CHANREQ(3, 3), + }, + [DMACH_I2S_IN] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(2, 1) | + S3C24XX_DMA_CHANREQ(1, 2), + }, + [DMACH_I2S_OUT] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(0, 2), }, + [DMACH_USB_EP1] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(4, 0), }, + [DMACH_USB_EP2] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(4, 1), }, + [DMACH_USB_EP3] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(4, 2), }, + [DMACH_USB_EP4] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(4, 3), }, +}; + +static struct s3c24xx_dma_platdata s3c2410_dma_platdata = { + .num_phy_channels = 4, + .channels = s3c2410_dma_channels, + .num_channels = DMACH_MAX, +}; + +struct platform_device s3c2410_device_dma = { + .name = "s3c2410-dma", + .id = 0, + .num_resources = ARRAY_SIZE(s3c2410_dma_resource), + .resource = s3c2410_dma_resource, + .dev = { + .platform_data = &s3c2410_dma_platdata, + }, +}; +#endif + #ifdef CONFIG_CPU_S3C2412 static struct s3c24xx_dma_channel s3c2412_dma_channels[DMACH_MAX] = { [DMACH_XD0] = { S3C24XX_DMA_AHB, true, 17 }, @@ -384,6 +428,62 @@ struct platform_device s3c2412_device_dma = { }; #endif +#if defined(CONFIG_CPU_S3C2440) +static struct s3c24xx_dma_channel s3c2440_dma_channels[DMACH_MAX] = { + [DMACH_XD0] = { S3C24XX_DMA_AHB, true, S3C24XX_DMA_CHANREQ(0, 0), }, + [DMACH_XD1] = { S3C24XX_DMA_AHB, true, S3C24XX_DMA_CHANREQ(0, 1), }, + [DMACH_SDI] = { S3C24XX_DMA_APB, false, S3C24XX_DMA_CHANREQ(2, 0) | + S3C24XX_DMA_CHANREQ(6, 1) | + S3C24XX_DMA_CHANREQ(2, 2) | + S3C24XX_DMA_CHANREQ(1, 3), + }, + [DMACH_SPI0] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(3, 1), }, + [DMACH_SPI1] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(2, 3), }, + [DMACH_UART0] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(1, 0), }, + [DMACH_UART1] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(1, 1), }, + [DMACH_UART2] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(0, 3), }, + [DMACH_TIMER] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(3, 0) | + S3C24XX_DMA_CHANREQ(3, 2) | + S3C24XX_DMA_CHANREQ(3, 3), + }, + [DMACH_I2S_IN] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(2, 1) | + S3C24XX_DMA_CHANREQ(1, 2), + }, + [DMACH_I2S_OUT] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(5, 0) | + S3C24XX_DMA_CHANREQ(0, 2), + }, + [DMACH_PCM_IN] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(6, 0) | + S3C24XX_DMA_CHANREQ(5, 2), + }, + [DMACH_PCM_OUT] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(5, 1) | + S3C24XX_DMA_CHANREQ(6, 3), + }, + [DMACH_MIC_IN] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(6, 2) | + S3C24XX_DMA_CHANREQ(5, 3), + }, + [DMACH_USB_EP1] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(4, 0), }, + [DMACH_USB_EP2] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(4, 1), }, + [DMACH_USB_EP3] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(4, 2), }, + [DMACH_USB_EP4] = { S3C24XX_DMA_APB, true, S3C24XX_DMA_CHANREQ(4, 3), }, +}; + +static struct s3c24xx_dma_platdata s3c2440_dma_platdata = { + .num_phy_channels = 4, + .channels = s3c2440_dma_channels, + .num_channels = DMACH_MAX, +}; + +struct platform_device s3c2440_device_dma = { + .name = "s3c2410-dma", + .id = 0, + .num_resources = ARRAY_SIZE(s3c2410_dma_resource), + .resource = s3c2410_dma_resource, + .dev = { + .platform_data = &s3c2440_dma_platdata, + }, +}; +#endif + #if defined(CONFIG_CPUS_3C2443) || defined(CONFIG_CPU_S3C2416) static struct resource s3c2443_dma_resource[] = { [0] = DEFINE_RES_MEM(S3C24XX_PA_DMA, S3C24XX_SZ_DMA), diff --git a/arch/arm/mach-s3c24xx/common.h b/arch/arm/mach-s3c24xx/common.h index fe071893275f..e46c10417216 100644 --- a/arch/arm/mach-s3c24xx/common.h +++ b/arch/arm/mach-s3c24xx/common.h @@ -109,7 +109,9 @@ extern void s3c2443_init_irq(void); extern struct syscore_ops s3c24xx_irq_syscore_ops; +extern struct platform_device s3c2410_device_dma; extern struct platform_device s3c2412_device_dma; +extern struct platform_device s3c2440_device_dma; extern struct platform_device s3c2443_device_dma; #endif /* __ARCH_ARM_MACH_S3C24XX_COMMON_H */ diff --git a/include/linux/platform_data/dma-s3c24xx.h b/include/linux/platform_data/dma-s3c24xx.h index 5a0cfffe3bbb..89ba1b0c90e4 100644 --- a/include/linux/platform_data/dma-s3c24xx.h +++ b/include/linux/platform_data/dma-s3c24xx.h @@ -9,6 +9,9 @@ * any later version. */ +/* Helper to encode the source selection constraints for early s3c socs. */ +#define S3C24XX_DMA_CHANREQ(src, chan) ((BIT(3) | src) << chan * 4) + enum s3c24xx_dma_bus { S3C24XX_DMA_APB, S3C24XX_DMA_AHB, -- cgit v1.2.3 From 602519b2bd63adfdf9e24b2f94aaddcfeb464e9e Mon Sep 17 00:00:00 2001 From: Dong Aisheng Date: Fri, 18 Oct 2013 19:48:47 +0800 Subject: mmc: sdhci-esdhc-imx: add delay line setting support The DLL(Delay Line) is newly added to assist in sampling read data. The DLL provides the ability to programmatically select a quantized delay (in fractions of the clock period) regardless of on-chip variations such as process, voltage and temperature (PVT). This patch adds a user interface to set slave delay line via device tree. It's usually used in high speed mode like mmc DDR mode when the signal quality is not good caused by board design, e.g. the signal path is too long. User can manually set delay line to find a suitable data sampling window for card to work properly. Signed-off-by: Dong Aisheng Acked-by: Shawn Guo Signed-off-by: Chris Ball --- .../devicetree/bindings/mmc/fsl-imx-esdhc.txt | 5 +++++ drivers/mmc/host/sdhci-esdhc-imx.c | 18 ++++++++++++++++++ include/linux/platform_data/mmc-esdhc-imx.h | 1 + 3 files changed, 24 insertions(+) (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/mmc/fsl-imx-esdhc.txt b/Documentation/devicetree/bindings/mmc/fsl-imx-esdhc.txt index 1dd622546d06..9046ba06c47a 100644 --- a/Documentation/devicetree/bindings/mmc/fsl-imx-esdhc.txt +++ b/Documentation/devicetree/bindings/mmc/fsl-imx-esdhc.txt @@ -12,6 +12,11 @@ Required properties: Optional properties: - fsl,cd-controller : Indicate to use controller internal card detection - fsl,wp-controller : Indicate to use controller internal write protection +- fsl,delay-line : Specify the number of delay cells for override mode. + This is used to set the clock delay for DLL(Delay Line) on override mode + to select a proper data sampling window in case the clock quality is not good + due to signal path is too long on the board. Please refer to eSDHC/uSDHC + chapter, DLL (Delay Line) section in RM for details. Examples: diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 915fa68e5122..260a81fde18d 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -46,6 +46,11 @@ /* Bits 3 and 6 are not SDHCI standard definitions */ #define ESDHC_MIX_CTRL_SDHCI_MASK 0xb7 +/* dll control register */ +#define ESDHC_DLL_CTRL 0x60 +#define ESDHC_DLL_OVERRIDE_VAL_SHIFT 9 +#define ESDHC_DLL_OVERRIDE_EN_SHIFT 8 + /* tune control register */ #define ESDHC_TUNE_CTRL_STATUS 0x68 #define ESDHC_TUNE_CTRL_STEP 1 @@ -817,6 +822,7 @@ static int esdhc_set_uhs_signaling(struct sdhci_host *host, unsigned int uhs) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct pltfm_imx_data *imx_data = pltfm_host->priv; + struct esdhc_platform_data *boarddata = &imx_data->boarddata; switch (uhs) { case MMC_TIMING_UHS_SDR12: @@ -837,6 +843,15 @@ static int esdhc_set_uhs_signaling(struct sdhci_host *host, unsigned int uhs) ESDHC_MIX_CTRL_DDREN, host->ioaddr + ESDHC_MIX_CTRL); imx_data->is_ddr = 1; + if (boarddata->delay_line) { + u32 v; + v = boarddata->delay_line << + ESDHC_DLL_OVERRIDE_VAL_SHIFT | + (1 << ESDHC_DLL_OVERRIDE_EN_SHIFT); + if (is_imx53_esdhc(imx_data)) + v <<= 1; + writel(v, host->ioaddr + ESDHC_DLL_CTRL); + } break; } @@ -901,6 +916,9 @@ sdhci_esdhc_imx_probe_dt(struct platform_device *pdev, else boarddata->support_vsel = true; + if (of_property_read_u32(np, "fsl,delay-line", &boarddata->delay_line)) + boarddata->delay_line = 0; + return 0; } #else diff --git a/include/linux/platform_data/mmc-esdhc-imx.h b/include/linux/platform_data/mmc-esdhc-imx.h index a0f5a8f9b3bc..75f70f6ac137 100644 --- a/include/linux/platform_data/mmc-esdhc-imx.h +++ b/include/linux/platform_data/mmc-esdhc-imx.h @@ -45,5 +45,6 @@ struct esdhc_platform_data { int max_bus_width; unsigned int f_max; bool support_vsel; + unsigned int delay_line; }; #endif /* __ASM_ARCH_IMX_ESDHC_H */ -- cgit v1.2.3 From 4023fe6ff2192d6050647571ea54f5497b2ec8f6 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 18 Oct 2013 18:37:43 +0300 Subject: ASoC: davinci-mcasp: Extract DMA channels directly from DT Extract DMA channels directly from DT as they can not be found from platform resources anymore. This is a work-around until davinci audio driver is updated to use dmaengine. Signed-off-by: Jyri Sarha Signed-off-by: Mark Brown --- .../bindings/sound/davinci-mcasp-audio.txt | 5 +++ include/linux/platform_data/davinci_asp.h | 2 + sound/soc/davinci/davinci-mcasp.c | 45 +++++++++++++++------- 3 files changed, 38 insertions(+), 14 deletions(-) (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/sound/davinci-mcasp-audio.txt b/Documentation/devicetree/bindings/sound/davinci-mcasp-audio.txt index c2ab8697e24a..c3ccde71f97a 100644 --- a/Documentation/devicetree/bindings/sound/davinci-mcasp-audio.txt +++ b/Documentation/devicetree/bindings/sound/davinci-mcasp-audio.txt @@ -18,6 +18,11 @@ Required properties: - serial-dir : A list of serializer pin mode. The list number should be equal to "num-serializer" parameter. Each entry is a number indication serializer pin direction. (0 - INACTIVE, 1 - TX, 2 - RX) +- dmas: two element list of DMA controller phandles and DMA request line + ordered pairs. +- dma-names: identifier string for each DMA request line in the dmas property. + These strings correspond 1:1 with the ordered pairs in dmas. The dma + identifiers must be "rx" and "tx". Optional properties: diff --git a/include/linux/platform_data/davinci_asp.h b/include/linux/platform_data/davinci_asp.h index 8db5ae03b6e3..689a856b86f9 100644 --- a/include/linux/platform_data/davinci_asp.h +++ b/include/linux/platform_data/davinci_asp.h @@ -84,6 +84,8 @@ struct snd_platform_data { u8 version; u8 txnumevt; u8 rxnumevt; + int tx_dma_channel; + int rx_dma_channel; }; enum { diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index 806bec34e4d9..4c207508348f 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -1047,6 +1047,7 @@ static struct snd_platform_data *davinci_mcasp_set_pdata_from_of( struct snd_platform_data *pdata = NULL; const struct of_device_id *match = of_match_device(mcasp_dt_ids, &pdev->dev); + struct of_phandle_args dma_spec; const u32 *of_serial_dir32; u8 *of_serial_dir; @@ -1109,6 +1110,28 @@ static struct snd_platform_data *davinci_mcasp_set_pdata_from_of( pdata->serial_dir = of_serial_dir; } + ret = of_property_match_string(np, "dma-names", "tx"); + if (ret < 0) + goto nodata; + + ret = of_parse_phandle_with_args(np, "dmas", "#dma-cells", ret, + &dma_spec); + if (ret < 0) + goto nodata; + + pdata->tx_dma_channel = dma_spec.args[0]; + + ret = of_property_match_string(np, "dma-names", "rx"); + if (ret < 0) + goto nodata; + + ret = of_parse_phandle_with_args(np, "dmas", "#dma-cells", ret, + &dma_spec); + if (ret < 0) + goto nodata; + + pdata->rx_dma_channel = dma_spec.args[0]; + ret = of_property_read_u32(np, "tx-num-evt", &val); if (ret >= 0) pdata->txnumevt = val; @@ -1213,15 +1236,11 @@ static int davinci_mcasp_probe(struct platform_device *pdev) dma_data->sram_size = pdata->sram_size_playback; dma_data->dma_addr = dat->start + pdata->tx_dma_offset; - /* first TX, then RX */ res = platform_get_resource(pdev, IORESOURCE_DMA, 0); - if (!res) { - dev_err(&pdev->dev, "no DMA resource\n"); - ret = -ENODEV; - goto err_release_clk; - } - - dma_data->channel = res->start; + if (res) + dma_data->channel = res->start; + else + dma_data->channel = pdata->tx_dma_channel; dma_data = &dev->dma_params[SNDRV_PCM_STREAM_CAPTURE]; dma_data->asp_chan_q = pdata->asp_chan_q; @@ -1231,13 +1250,11 @@ static int davinci_mcasp_probe(struct platform_device *pdev) dma_data->dma_addr = dat->start + pdata->rx_dma_offset; res = platform_get_resource(pdev, IORESOURCE_DMA, 1); - if (!res) { - dev_err(&pdev->dev, "no DMA resource\n"); - ret = -ENODEV; - goto err_release_clk; - } + if (res) + dma_data->channel = res->start; + else + dma_data->channel = pdata->rx_dma_channel; - dma_data->channel = res->start; dev_set_drvdata(&pdev->dev, dev); ret = snd_soc_register_component(&pdev->dev, &davinci_mcasp_component, &davinci_mcasp_dai[pdata->op_mode], 1); -- cgit v1.2.3 From f65f0a1a9836abbfbe5c9b8fa0452e4d8eb7bf00 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sun, 15 Sep 2013 03:50:17 -0700 Subject: leds: lp55xx: enable setting default trigger This enables setting a default trigger on an LP55xx channel, either from platform data or device tree. This mechanism is identical to the mechanism for GPIO LEDs and references the common LEDs device tree bindings. Signed-off-by: Linus Walleij Tested-by: Milo Kim Acked-by: Milo Kim Signed-off-by: Bryan Wu --- Documentation/devicetree/bindings/leds/leds-lp55xx.txt | 10 +++++++--- drivers/leds/leds-lp55xx-common.c | 3 +++ include/linux/platform_data/leds-lp55xx.h | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/leds/leds-lp55xx.txt b/Documentation/devicetree/bindings/leds/leds-lp55xx.txt index a61727f9a6d1..d221e75d90fa 100644 --- a/Documentation/devicetree/bindings/leds/leds-lp55xx.txt +++ b/Documentation/devicetree/bindings/leds/leds-lp55xx.txt @@ -17,12 +17,15 @@ Optional properties: 2: D1~6 with VOUT, D7~9 with VDD 3: D1~9 are connected to VOUT -Alternatively, each child can have specific channel name -- chan-name: Name of each channel name +Alternatively, each child can have a specific channel name and trigger: +- chan-name (optional): name of channel +- linux,default-trigger (optional): see + Documentation/devicetree/bindings/leds/common.txt example 1) LP5521 3 LED channels, external clock used. Channel names are 'lp5521_pri:channel0', -'lp5521_pri:channel1' and 'lp5521_pri:channel2' +'lp5521_pri:channel1' and 'lp5521_pri:channel2', with a heartbeat trigger +on channel 0. lp5521@32 { compatible = "national,lp5521"; @@ -33,6 +36,7 @@ lp5521@32 { chan0 { led-cur = /bits/ 8 <0x2f>; max-cur = /bits/ 8 <0x5f>; + linux,default-trigger = "heartbeat"; }; chan1 { diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c index 351825b96f16..075acf5b9fab 100644 --- a/drivers/leds/leds-lp55xx-common.c +++ b/drivers/leds/leds-lp55xx-common.c @@ -165,6 +165,7 @@ static int lp55xx_init_led(struct lp55xx_led *led, led->led_current = pdata->led_config[chan].led_current; led->max_current = pdata->led_config[chan].max_current; led->chan_nr = pdata->led_config[chan].chan_nr; + led->cdev.default_trigger = pdata->led_config[chan].default_trigger; if (led->chan_nr >= max_channel) { dev_err(dev, "Use channel numbers between 0 and %d\n", @@ -586,6 +587,8 @@ int lp55xx_of_populate_pdata(struct device *dev, struct device_node *np) of_property_read_string(child, "chan-name", &cfg[i].name); of_property_read_u8(child, "led-cur", &cfg[i].led_current); of_property_read_u8(child, "max-cur", &cfg[i].max_current); + cfg[i].default_trigger = + of_get_property(child, "linux,default-trigger", NULL); i++; } diff --git a/include/linux/platform_data/leds-lp55xx.h b/include/linux/platform_data/leds-lp55xx.h index 51a2ff579d60..c32de4dcec54 100644 --- a/include/linux/platform_data/leds-lp55xx.h +++ b/include/linux/platform_data/leds-lp55xx.h @@ -22,6 +22,7 @@ struct lp55xx_led_config { const char *name; + const char *default_trigger; u8 chan_nr; u8 led_current; /* mA x10, 0 if led is not connected */ u8 max_current; -- cgit v1.2.3 From bb6febdc90efe7f664328075c204eed8e9af7ec9 Mon Sep 17 00:00:00 2001 From: Maximilian Güntner Date: Wed, 16 Oct 2013 18:09:17 -0700 Subject: leds: Added driver for the NXP PCA9685 I2C chip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The NXP PCA9685 supports 16 channels/leds using a 12-bit PWM (4095 levels of brightness) This driver supports configuration using platform_data. Signed-off-by: Maximilian Güntner Signed-off-by: Bryan Wu --- drivers/leds/Kconfig | 10 ++ drivers/leds/Makefile | 1 + drivers/leds/leds-pca9685.c | 213 +++++++++++++++++++++++++++++ include/linux/platform_data/leds-pca9685.h | 35 +++++ 4 files changed, 259 insertions(+) create mode 100644 drivers/leds/leds-pca9685.c create mode 100644 include/linux/platform_data/leds-pca9685.h (limited to 'include/linux/platform_data') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 875bbe4c962e..72156c123033 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -300,6 +300,16 @@ config LEDS_PCA963X LED driver chip accessed via the I2C bus. Supported devices include PCA9633 and PCA9634 +config LEDS_PCA9685 + tristate "LED support for PCA9685 I2C chip" + depends on LEDS_CLASS + depends on I2C + help + This option enables support for LEDs connected to the PCA9685 + LED driver chip accessed via the I2C bus. + The PCA9685 offers 12-bit PWM (4095 levels of brightness) on + 16 individual channels. + config LEDS_WM831X_STATUS tristate "LED support for status LEDs on WM831x PMICs" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 8979b0b2c85e..3cd76dbd9be2 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_LEDS_OT200) += leds-ot200.o obj-$(CONFIG_LEDS_FSG) += leds-fsg.o obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o obj-$(CONFIG_LEDS_PCA963X) += leds-pca963x.o +obj-$(CONFIG_LEDS_PCA9685) += leds-pca9685.o obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o obj-$(CONFIG_LEDS_DA9052) += leds-da9052.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o diff --git a/drivers/leds/leds-pca9685.c b/drivers/leds/leds-pca9685.c new file mode 100644 index 000000000000..6e1ef3a9d6ef --- /dev/null +++ b/drivers/leds/leds-pca9685.c @@ -0,0 +1,213 @@ +/* + * Copyright 2013 Maximilian Güntner + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * Based on leds-pca963x.c driver by + * Peter Meerwald + * + * Driver for the NXP PCA9685 12-Bit PWM LED driver chip. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Register Addresses */ +#define PCA9685_MODE1 0x00 +#define PCA9685_MODE2 0x01 +#define PCA9685_LED0_ON_L 0x06 +#define PCA9685_ALL_LED_ON_L 0xFA + +/* MODE1 Register */ +#define PCA9685_ALLCALL 0x00 +#define PCA9685_SLEEP 0x04 +#define PCA9685_AI 0x05 + +/* MODE2 Register */ +#define PCA9685_INVRT 0x04 +#define PCA9685_OUTDRV 0x02 + +static const struct i2c_device_id pca9685_id[] = { + { "pca9685", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pca9685_id); + +struct pca9685_led { + struct i2c_client *client; + struct work_struct work; + u16 brightness; + struct led_classdev led_cdev; + int led_num; /* 0-15 */ + char name[32]; +}; + +static void pca9685_write_msg(struct i2c_client *client, u8 *buf, u8 len) +{ + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0x00, + .len = len, + .buf = buf + }; + i2c_transfer(client->adapter, &msg, 1); +} + +static void pca9685_all_off(struct i2c_client *client) +{ + u8 i2c_buffer[5] = {PCA9685_ALL_LED_ON_L, 0x00, 0x00, 0x00, 0x10}; + pca9685_write_msg(client, i2c_buffer, 5); +} + +static void pca9685_led_work(struct work_struct *work) +{ + struct pca9685_led *pca9685; + u8 i2c_buffer[5]; + + pca9685 = container_of(work, struct pca9685_led, work); + i2c_buffer[0] = PCA9685_LED0_ON_L + 4 * pca9685->led_num; + /* + * 4095 is the maximum brightness, so we set the ON time to 0x1000 + * which disables the PWM generator for that LED + */ + if (pca9685->brightness == 4095) + *((__le16 *)(i2c_buffer+1)) = cpu_to_le16(0x1000); + else + *((__le16 *)(i2c_buffer+1)) = 0x0000; + + if (pca9685->brightness == 0) + *((__le16 *)(i2c_buffer+3)) = cpu_to_le16(0x1000); + else if (pca9685->brightness == 4095) + *((__le16 *)(i2c_buffer+3)) = 0x0000; + else + *((__le16 *)(i2c_buffer+3)) = cpu_to_le16(pca9685->brightness); + + pca9685_write_msg(pca9685->client, i2c_buffer, 5); +} + +static void pca9685_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct pca9685_led *pca9685; + pca9685 = container_of(led_cdev, struct pca9685_led, led_cdev); + pca9685->brightness = value; + + schedule_work(&pca9685->work); +} + +static int pca9685_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pca9685_led *pca9685; + struct pca9685_platform_data *pdata; + int err; + u8 i; + + pdata = dev_get_platdata(&client->dev); + if (pdata) { + if (pdata->leds.num_leds < 1 || pdata->leds.num_leds > 15) { + dev_err(&client->dev, "board info must claim 1-16 LEDs"); + return -EINVAL; + } + } + + pca9685 = devm_kzalloc(&client->dev, 16 * sizeof(*pca9685), GFP_KERNEL); + if (!pca9685) + return -ENOMEM; + + i2c_set_clientdata(client, pca9685); + pca9685_all_off(client); + + for (i = 0; i < 16; i++) { + pca9685[i].client = client; + pca9685[i].led_num = i; + pca9685[i].name[0] = '\0'; + if (pdata && i < pdata->leds.num_leds) { + if (pdata->leds.leds[i].name) + strncpy(pca9685[i].name, + pdata->leds.leds[i].name, + sizeof(pca9685[i].name)-1); + if (pdata->leds.leds[i].default_trigger) + pca9685[i].led_cdev.default_trigger = + pdata->leds.leds[i].default_trigger; + } + if (strlen(pca9685[i].name) == 0) { + /* + * Write adapter and address to the name as well. + * Otherwise multiple chips attached to one host would + * not work. + */ + snprintf(pca9685[i].name, sizeof(pca9685[i].name), + "pca9685:%d:x%.2x:%d", + client->adapter->nr, client->addr, i); + } + pca9685[i].led_cdev.name = pca9685[i].name; + pca9685[i].led_cdev.max_brightness = 0xfff; + pca9685[i].led_cdev.brightness_set = pca9685_led_set; + + INIT_WORK(&pca9685[i].work, pca9685_led_work); + err = led_classdev_register(&client->dev, &pca9685[i].led_cdev); + if (err < 0) + goto exit; + } + + if (pdata) + i2c_smbus_write_byte_data(client, PCA9685_MODE2, + pdata->outdrv << PCA9685_OUTDRV | + pdata->inverted << PCA9685_INVRT); + else + i2c_smbus_write_byte_data(client, PCA9685_MODE2, + PCA9685_TOTEM_POLE << PCA9685_OUTDRV); + /* Enable Auto-Increment, enable oscillator, ALLCALL/SUBADDR disabled */ + i2c_smbus_write_byte_data(client, PCA9685_MODE1, BIT(PCA9685_AI)); + + return 0; + +exit: + while (i--) { + led_classdev_unregister(&pca9685[i].led_cdev); + cancel_work_sync(&pca9685[i].work); + } + return err; +} + +static int pca9685_remove(struct i2c_client *client) +{ + struct pca9685_led *pca9685 = i2c_get_clientdata(client); + u8 i; + + for (i = 0; i < 16; i++) { + led_classdev_unregister(&pca9685[i].led_cdev); + cancel_work_sync(&pca9685[i].work); + } + pca9685_all_off(client); + return 0; +} + +static struct i2c_driver pca9685_driver = { + .driver = { + .name = "leds-pca9685", + .owner = THIS_MODULE, + }, + .probe = pca9685_probe, + .remove = pca9685_remove, + .id_table = pca9685_id, +}; + +module_i2c_driver(pca9685_driver); + +MODULE_AUTHOR("Maximilian Güntner "); +MODULE_DESCRIPTION("PCA9685 LED Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/platform_data/leds-pca9685.h b/include/linux/platform_data/leds-pca9685.h new file mode 100644 index 000000000000..778e9e4249cc --- /dev/null +++ b/include/linux/platform_data/leds-pca9685.h @@ -0,0 +1,35 @@ +/* + * Copyright 2013 Maximilian Güntner + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * Based on leds-pca963x.h by Peter Meerwald + * + * LED driver for the NXP PCA9685 PWM chip + * + */ + +#ifndef __LINUX_PCA9685_H +#define __LINUX_PCA9685_H + +#include + +enum pca9685_outdrv { + PCA9685_OPEN_DRAIN, + PCA9685_TOTEM_POLE, +}; + +enum pca9685_inverted { + PCA9685_NOT_INVERTED, + PCA9685_INVERTED, +}; + +struct pca9685_platform_data { + struct led_platform_data leds; + enum pca9685_outdrv outdrv; + enum pca9685_inverted inverted; +}; + +#endif /* __LINUX_PCA9685_H */ -- cgit v1.2.3 From 30dae2f98612d7c8cd855861b9de205ebd9ef4fa Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 22 Oct 2013 11:02:56 -0700 Subject: leds: lp55xx: handle enable pin in driver This patch moves the handling of the chip's enable pin from the board code into the driver. It also updates all board-code files using the driver to incorporate this change. This is needed for device tree support of the enable pin. Signed-off-by: Sebastian Reichel Acked-by: Linus Walleij Acked-by: Tony Lindgren Signed-off-by: Bryan Wu --- .../devicetree/bindings/leds/leds-lp55xx.txt | 1 + arch/arm/mach-omap2/board-rx51-peripherals.c | 20 +---------------- arch/arm/mach-ux500/board-mop500.c | 2 ++ drivers/leds/leds-lp55xx-common.c | 25 +++++++++++----------- include/linux/platform_data/leds-lp55xx.h | 6 ++---- 5 files changed, 19 insertions(+), 35 deletions(-) (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/leds/leds-lp55xx.txt b/Documentation/devicetree/bindings/leds/leds-lp55xx.txt index d221e75d90fa..c55b8c016a9e 100644 --- a/Documentation/devicetree/bindings/leds/leds-lp55xx.txt +++ b/Documentation/devicetree/bindings/leds/leds-lp55xx.txt @@ -10,6 +10,7 @@ Each child has own specific current settings - max-cur: Maximun current at each led channel. Optional properties: +- enable-gpio: GPIO attached to the chip's enable pin - label: Used for naming LEDs - pwr-sel: LP8501 specific property. Power selection for output channels. 0: D1~9 are connected to VDD diff --git a/arch/arm/mach-omap2/board-rx51-peripherals.c b/arch/arm/mach-omap2/board-rx51-peripherals.c index f6fe388af989..68dc998fa34b 100644 --- a/arch/arm/mach-omap2/board-rx51-peripherals.c +++ b/arch/arm/mach-omap2/board-rx51-peripherals.c @@ -211,29 +211,11 @@ static struct lp55xx_led_config rx51_lp5523_led_config[] = { } }; -static int rx51_lp5523_setup(void) -{ - return gpio_request_one(RX51_LP5523_CHIP_EN_GPIO, GPIOF_DIR_OUT, - "lp5523_enable"); -} - -static void rx51_lp5523_release(void) -{ - gpio_free(RX51_LP5523_CHIP_EN_GPIO); -} - -static void rx51_lp5523_enable(bool state) -{ - gpio_set_value(RX51_LP5523_CHIP_EN_GPIO, !!state); -} - static struct lp55xx_platform_data rx51_lp5523_platform_data = { .led_config = rx51_lp5523_led_config, .num_channels = ARRAY_SIZE(rx51_lp5523_led_config), .clock_mode = LP55XX_CLOCK_AUTO, - .setup_resources = rx51_lp5523_setup, - .release_resources = rx51_lp5523_release, - .enable = rx51_lp5523_enable, + .enable_gpio = RX51_LP5523_CHIP_EN_GPIO, }; #endif diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c index ad0806eff762..703dec2b7d8d 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -297,6 +297,7 @@ static struct lp55xx_platform_data __initdata lp5521_pri_data = { .led_config = &lp5521_pri_led[0], .num_channels = 3, .clock_mode = LP55XX_CLOCK_EXT, + .enable_gpio = -1, }; static struct lp55xx_led_config lp5521_sec_led[] = { @@ -322,6 +323,7 @@ static struct lp55xx_platform_data __initdata lp5521_sec_data = { .led_config = &lp5521_sec_led[0], .num_channels = 3, .clock_mode = LP55XX_CLOCK_EXT, + .enable_gpio = -1, }; /* I2C0 devices only available on the first HREF/MOP500 */ diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c index 075acf5b9fab..9acc6bb7deef 100644 --- a/drivers/leds/leds-lp55xx-common.c +++ b/drivers/leds/leds-lp55xx-common.c @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include "leds-lp55xx-common.h" @@ -407,18 +409,18 @@ int lp55xx_init_device(struct lp55xx_chip *chip) if (!pdata || !cfg) return -EINVAL; - if (pdata->setup_resources) { - ret = pdata->setup_resources(); + if (gpio_is_valid(pdata->enable_gpio)) { + ret = devm_gpio_request_one(dev, pdata->enable_gpio, + GPIOF_DIR_OUT, "lp5523_enable"); if (ret < 0) { - dev_err(dev, "setup resoure err: %d\n", ret); + dev_err(dev, "could not acquire enable gpio (err=%d)\n", + ret); goto err; } - } - if (pdata->enable) { - pdata->enable(0); + gpio_set_value(pdata->enable_gpio, 0); usleep_range(1000, 2000); /* Keep enable down at least 1ms */ - pdata->enable(1); + gpio_set_value(pdata->enable_gpio, 1); usleep_range(1000, 2000); /* 500us abs min. */ } @@ -459,11 +461,8 @@ void lp55xx_deinit_device(struct lp55xx_chip *chip) if (chip->clk) clk_disable_unprepare(chip->clk); - if (pdata->enable) - pdata->enable(0); - - if (pdata->release_resources) - pdata->release_resources(); + if (gpio_is_valid(pdata->enable_gpio)) + gpio_set_value(pdata->enable_gpio, 0); } EXPORT_SYMBOL_GPL(lp55xx_deinit_device); @@ -596,6 +595,8 @@ int lp55xx_of_populate_pdata(struct device *dev, struct device_node *np) of_property_read_string(np, "label", &pdata->label); of_property_read_u8(np, "clock-mode", &pdata->clock_mode); + pdata->enable_gpio = of_get_named_gpio(np, "enable-gpio", 0); + /* LP8501 specific */ of_property_read_u8(np, "pwr-sel", (u8 *)&pdata->pwr_sel); diff --git a/include/linux/platform_data/leds-lp55xx.h b/include/linux/platform_data/leds-lp55xx.h index c32de4dcec54..624ff9edad6f 100644 --- a/include/linux/platform_data/leds-lp55xx.h +++ b/include/linux/platform_data/leds-lp55xx.h @@ -67,10 +67,8 @@ struct lp55xx_platform_data { /* Clock configuration */ u8 clock_mode; - /* Platform specific functions */ - int (*setup_resources)(void); - void (*release_resources)(void); - void (*enable)(bool state); + /* optional enable GPIO */ + int enable_gpio; /* Predefined pattern data */ struct lp55xx_predef_pattern *patterns; -- cgit v1.2.3 From db60d8da8f643586c95f8fc3e383954f8c57f1f3 Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Wed, 30 Oct 2013 18:22:30 +0530 Subject: dmanengine: fix edma driver to not define DMA_COMPLETE edma header defines DMA_COMPLETE, this causes issues as commit adfedd9a32e4 move DMA_SUCCESS to DMA_COMPLETE. edma should properly namespace its defines and needs a future fix Reported-by: Olof Johansson Signed-off-by: Vinod Koul --- arch/arm/common/edma.c | 4 ++-- drivers/dma/edma.c | 4 ++-- include/linux/platform_data/edma.h | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'include/linux/platform_data') diff --git a/arch/arm/common/edma.c b/arch/arm/common/edma.c index 8e1a0245907f..41bca32409fc 100644 --- a/arch/arm/common/edma.c +++ b/arch/arm/common/edma.c @@ -404,7 +404,7 @@ static irqreturn_t dma_irq_handler(int irq, void *data) BIT(slot)); if (edma_cc[ctlr]->intr_data[channel].callback) edma_cc[ctlr]->intr_data[channel].callback( - channel, DMA_COMPLETE, + channel, EDMA_DMA_COMPLETE, edma_cc[ctlr]->intr_data[channel].data); } } while (sh_ipr); @@ -459,7 +459,7 @@ static irqreturn_t dma_ccerr_handler(int irq, void *data) callback) { edma_cc[ctlr]->intr_data[k]. callback(k, - DMA_CC_ERROR, + EDMA_DMA_CC_ERROR, edma_cc[ctlr]->intr_data [k].data); } diff --git a/drivers/dma/edma.c b/drivers/dma/edma.c index 29fa35807f09..cb69f3a674e0 100644 --- a/drivers/dma/edma.c +++ b/drivers/dma/edma.c @@ -407,7 +407,7 @@ static void edma_callback(unsigned ch_num, u16 ch_status, void *data) edma_pause(echan->ch_num); switch (ch_status) { - case DMA_COMPLETE: + case EDMA_DMA_COMPLETE: spin_lock_irqsave(&echan->vchan.lock, flags); edesc = echan->edesc; @@ -426,7 +426,7 @@ static void edma_callback(unsigned ch_num, u16 ch_status, void *data) spin_unlock_irqrestore(&echan->vchan.lock, flags); break; - case DMA_CC_ERROR: + case EDMA_DMA_CC_ERROR: spin_lock_irqsave(&echan->vchan.lock, flags); edma_read_slot(EDMA_CHAN_SLOT(echan->slot[0]), &p); diff --git a/include/linux/platform_data/edma.h b/include/linux/platform_data/edma.h index 179fb91bb5f2..f50821cb64be 100644 --- a/include/linux/platform_data/edma.h +++ b/include/linux/platform_data/edma.h @@ -67,10 +67,10 @@ struct edmacc_param { #define ITCCHEN BIT(23) /*ch_status paramater of callback function possible values*/ -#define DMA_COMPLETE 1 -#define DMA_CC_ERROR 2 -#define DMA_TC1_ERROR 3 -#define DMA_TC2_ERROR 4 +#define EDMA_DMA_COMPLETE 1 +#define EDMA_DMA_CC_ERROR 2 +#define EDMA_DMA_TC1_ERROR 3 +#define EDMA_DMA_TC2_ERROR 4 enum address_mode { INCR = 0, -- cgit v1.2.3 From c6d81bd7237b5785080087bcf796ce456ef69557 Mon Sep 17 00:00:00 2001 From: Heiko Stübner Date: Thu, 31 Oct 2013 01:25:32 -0700 Subject: Input: add driver for Neonode zForce based touchscreens This adds a driver for touchscreens using the zforce infrared technology from Neonode connected via i2c to the host system. It supports multitouch with up to two fingers and tracking of the contacts in hardware. Signed-off-by: Heiko Stuebner Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/Kconfig | 13 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/zforce_ts.c | 836 ++++++++++++++++++++++++++++++++ include/linux/platform_data/zforce_ts.h | 26 + 4 files changed, 876 insertions(+) create mode 100644 drivers/input/touchscreen/zforce_ts.c create mode 100644 include/linux/platform_data/zforce_ts.h (limited to 'include/linux/platform_data') diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index e09ec67957a3..00d1e547b211 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -919,4 +919,17 @@ config TOUCHSCREEN_TPS6507X To compile this driver as a module, choose M here: the module will be called tps6507x_ts. +config TOUCHSCREEN_ZFORCE + tristate "Neonode zForce infrared touchscreens" + depends on I2C + depends on GPIOLIB + help + Say Y here if you have a touchscreen using the zforce + infraread technology from Neonode. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called zforce_ts. + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index f5216c1bf53e..7587883b8d38 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -75,3 +75,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o +obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o diff --git a/drivers/input/touchscreen/zforce_ts.c b/drivers/input/touchscreen/zforce_ts.c new file mode 100644 index 000000000000..75762d6ff3ba --- /dev/null +++ b/drivers/input/touchscreen/zforce_ts.c @@ -0,0 +1,836 @@ +/* + * Copyright (C) 2012-2013 MundoReader S.L. + * Author: Heiko Stuebner + * + * based in parts on Nook zforce driver + * + * Copyright (C) 2010 Barnes & Noble, Inc. + * Author: Pieter Truter + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WAIT_TIMEOUT msecs_to_jiffies(1000) + +#define FRAME_START 0xee + +/* Offsets of the different parts of the payload the controller sends */ +#define PAYLOAD_HEADER 0 +#define PAYLOAD_LENGTH 1 +#define PAYLOAD_BODY 2 + +/* Response offsets */ +#define RESPONSE_ID 0 +#define RESPONSE_DATA 1 + +/* Commands */ +#define COMMAND_DEACTIVATE 0x00 +#define COMMAND_INITIALIZE 0x01 +#define COMMAND_RESOLUTION 0x02 +#define COMMAND_SETCONFIG 0x03 +#define COMMAND_DATAREQUEST 0x04 +#define COMMAND_SCANFREQ 0x08 +#define COMMAND_STATUS 0X1e + +/* + * Responses the controller sends as a result of + * command requests + */ +#define RESPONSE_DEACTIVATE 0x00 +#define RESPONSE_INITIALIZE 0x01 +#define RESPONSE_RESOLUTION 0x02 +#define RESPONSE_SETCONFIG 0x03 +#define RESPONSE_SCANFREQ 0x08 +#define RESPONSE_STATUS 0X1e + +/* + * Notifications are send by the touch controller without + * being requested by the driver and include for example + * touch indications + */ +#define NOTIFICATION_TOUCH 0x04 +#define NOTIFICATION_BOOTCOMPLETE 0x07 +#define NOTIFICATION_OVERRUN 0x25 +#define NOTIFICATION_PROXIMITY 0x26 +#define NOTIFICATION_INVALID_COMMAND 0xfe + +#define ZFORCE_REPORT_POINTS 2 +#define ZFORCE_MAX_AREA 0xff + +#define STATE_DOWN 0 +#define STATE_MOVE 1 +#define STATE_UP 2 + +#define SETCONFIG_DUALTOUCH (1 << 0) + +struct zforce_point { + int coord_x; + int coord_y; + int state; + int id; + int area_major; + int area_minor; + int orientation; + int pressure; + int prblty; +}; + +/* + * @client the i2c_client + * @input the input device + * @suspending in the process of going to suspend (don't emit wakeup + * events for commands executed to suspend the device) + * @suspended device suspended + * @access_mutex serialize i2c-access, to keep multipart reads together + * @command_done completion to wait for the command result + * @command_mutex serialize commands send to the ic + * @command_waiting the id of the command that that is currently waiting + * for a result + * @command_result returned result of the command + */ +struct zforce_ts { + struct i2c_client *client; + struct input_dev *input; + const struct zforce_ts_platdata *pdata; + char phys[32]; + + bool suspending; + bool suspended; + bool boot_complete; + + /* Firmware version information */ + u16 version_major; + u16 version_minor; + u16 version_build; + u16 version_rev; + + struct mutex access_mutex; + + struct completion command_done; + struct mutex command_mutex; + int command_waiting; + int command_result; +}; + +static int zforce_command(struct zforce_ts *ts, u8 cmd) +{ + struct i2c_client *client = ts->client; + char buf[3]; + int ret; + + dev_dbg(&client->dev, "%s: 0x%x\n", __func__, cmd); + + buf[0] = FRAME_START; + buf[1] = 1; /* data size, command only */ + buf[2] = cmd; + + mutex_lock(&ts->access_mutex); + ret = i2c_master_send(client, &buf[0], ARRAY_SIZE(buf)); + mutex_unlock(&ts->access_mutex); + if (ret < 0) { + dev_err(&client->dev, "i2c send data request error: %d\n", ret); + return ret; + } + + return 0; +} + +static int zforce_send_wait(struct zforce_ts *ts, const char *buf, int len) +{ + struct i2c_client *client = ts->client; + int ret; + + ret = mutex_trylock(&ts->command_mutex); + if (!ret) { + dev_err(&client->dev, "already waiting for a command\n"); + return -EBUSY; + } + + dev_dbg(&client->dev, "sending %d bytes for command 0x%x\n", + buf[1], buf[2]); + + ts->command_waiting = buf[2]; + + mutex_lock(&ts->access_mutex); + ret = i2c_master_send(client, buf, len); + mutex_unlock(&ts->access_mutex); + if (ret < 0) { + dev_err(&client->dev, "i2c send data request error: %d\n", ret); + goto unlock; + } + + dev_dbg(&client->dev, "waiting for result for command 0x%x\n", buf[2]); + + if (wait_for_completion_timeout(&ts->command_done, WAIT_TIMEOUT) == 0) { + ret = -ETIME; + goto unlock; + } + + ret = ts->command_result; + +unlock: + mutex_unlock(&ts->command_mutex); + return ret; +} + +static int zforce_command_wait(struct zforce_ts *ts, u8 cmd) +{ + struct i2c_client *client = ts->client; + char buf[3]; + int ret; + + dev_dbg(&client->dev, "%s: 0x%x\n", __func__, cmd); + + buf[0] = FRAME_START; + buf[1] = 1; /* data size, command only */ + buf[2] = cmd; + + ret = zforce_send_wait(ts, &buf[0], ARRAY_SIZE(buf)); + if (ret < 0) { + dev_err(&client->dev, "i2c send data request error: %d\n", ret); + return ret; + } + + return 0; +} + +static int zforce_resolution(struct zforce_ts *ts, u16 x, u16 y) +{ + struct i2c_client *client = ts->client; + char buf[7] = { FRAME_START, 5, COMMAND_RESOLUTION, + (x & 0xff), ((x >> 8) & 0xff), + (y & 0xff), ((y >> 8) & 0xff) }; + + dev_dbg(&client->dev, "set resolution to (%d,%d)\n", x, y); + + return zforce_send_wait(ts, &buf[0], ARRAY_SIZE(buf)); +} + +static int zforce_scan_frequency(struct zforce_ts *ts, u16 idle, u16 finger, + u16 stylus) +{ + struct i2c_client *client = ts->client; + char buf[9] = { FRAME_START, 7, COMMAND_SCANFREQ, + (idle & 0xff), ((idle >> 8) & 0xff), + (finger & 0xff), ((finger >> 8) & 0xff), + (stylus & 0xff), ((stylus >> 8) & 0xff) }; + + dev_dbg(&client->dev, "set scan frequency to (idle: %d, finger: %d, stylus: %d)\n", + idle, finger, stylus); + + return zforce_send_wait(ts, &buf[0], ARRAY_SIZE(buf)); +} + +static int zforce_setconfig(struct zforce_ts *ts, char b1) +{ + struct i2c_client *client = ts->client; + char buf[7] = { FRAME_START, 5, COMMAND_SETCONFIG, + b1, 0, 0, 0 }; + + dev_dbg(&client->dev, "set config to (%d)\n", b1); + + return zforce_send_wait(ts, &buf[0], ARRAY_SIZE(buf)); +} + +static int zforce_start(struct zforce_ts *ts) +{ + struct i2c_client *client = ts->client; + const struct zforce_ts_platdata *pdata = dev_get_platdata(&client->dev); + int ret; + + dev_dbg(&client->dev, "starting device\n"); + + ret = zforce_command_wait(ts, COMMAND_INITIALIZE); + if (ret) { + dev_err(&client->dev, "Unable to initialize, %d\n", ret); + return ret; + } + + ret = zforce_resolution(ts, pdata->x_max, pdata->y_max); + if (ret) { + dev_err(&client->dev, "Unable to set resolution, %d\n", ret); + goto error; + } + + ret = zforce_scan_frequency(ts, 10, 50, 50); + if (ret) { + dev_err(&client->dev, "Unable to set scan frequency, %d\n", + ret); + goto error; + } + + if (zforce_setconfig(ts, SETCONFIG_DUALTOUCH)) { + dev_err(&client->dev, "Unable to set config\n"); + goto error; + } + + /* start sending touch events */ + ret = zforce_command(ts, COMMAND_DATAREQUEST); + if (ret) { + dev_err(&client->dev, "Unable to request data\n"); + goto error; + } + + /* + * Per NN, initial cal. take max. of 200msec. + * Allow time to complete this calibration + */ + msleep(200); + + return 0; + +error: + zforce_command_wait(ts, COMMAND_DEACTIVATE); + return ret; +} + +static int zforce_stop(struct zforce_ts *ts) +{ + struct i2c_client *client = ts->client; + int ret; + + dev_dbg(&client->dev, "stopping device\n"); + + /* Deactivates touch sensing and puts the device into sleep. */ + ret = zforce_command_wait(ts, COMMAND_DEACTIVATE); + if (ret != 0) { + dev_err(&client->dev, "could not deactivate device, %d\n", + ret); + return ret; + } + + return 0; +} + +static int zforce_touch_event(struct zforce_ts *ts, u8 *payload) +{ + struct i2c_client *client = ts->client; + const struct zforce_ts_platdata *pdata = dev_get_platdata(&client->dev); + struct zforce_point point; + int count, i, num = 0; + + count = payload[0]; + if (count > ZFORCE_REPORT_POINTS) { + dev_warn(&client->dev, "to many coordinates %d, expected max %d\n", + count, ZFORCE_REPORT_POINTS); + count = ZFORCE_REPORT_POINTS; + } + + for (i = 0; i < count; i++) { + point.coord_x = + payload[9 * i + 2] << 8 | payload[9 * i + 1]; + point.coord_y = + payload[9 * i + 4] << 8 | payload[9 * i + 3]; + + if (point.coord_x > pdata->x_max || + point.coord_y > pdata->y_max) { + dev_warn(&client->dev, "coordinates (%d,%d) invalid\n", + point.coord_x, point.coord_y); + point.coord_x = point.coord_y = 0; + } + + point.state = payload[9 * i + 5] & 0x03; + point.id = (payload[9 * i + 5] & 0xfc) >> 2; + + /* determine touch major, minor and orientation */ + point.area_major = max(payload[9 * i + 6], + payload[9 * i + 7]); + point.area_minor = min(payload[9 * i + 6], + payload[9 * i + 7]); + point.orientation = payload[9 * i + 6] > payload[9 * i + 7]; + + point.pressure = payload[9 * i + 8]; + point.prblty = payload[9 * i + 9]; + + dev_dbg(&client->dev, + "point %d/%d: state %d, id %d, pressure %d, prblty %d, x %d, y %d, amajor %d, aminor %d, ori %d\n", + i, count, point.state, point.id, + point.pressure, point.prblty, + point.coord_x, point.coord_y, + point.area_major, point.area_minor, + point.orientation); + + /* the zforce id starts with "1", so needs to be decreased */ + input_mt_slot(ts->input, point.id - 1); + + input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, + point.state != STATE_UP); + + if (point.state != STATE_UP) { + input_report_abs(ts->input, ABS_MT_POSITION_X, + point.coord_x); + input_report_abs(ts->input, ABS_MT_POSITION_Y, + point.coord_y); + input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, + point.area_major); + input_report_abs(ts->input, ABS_MT_TOUCH_MINOR, + point.area_minor); + input_report_abs(ts->input, ABS_MT_ORIENTATION, + point.orientation); + num++; + } + } + + input_mt_sync_frame(ts->input); + + input_mt_report_finger_count(ts->input, num); + + input_sync(ts->input); + + return 0; +} + +static int zforce_read_packet(struct zforce_ts *ts, u8 *buf) +{ + struct i2c_client *client = ts->client; + int ret; + + mutex_lock(&ts->access_mutex); + + /* read 2 byte message header */ + ret = i2c_master_recv(client, buf, 2); + if (ret < 0) { + dev_err(&client->dev, "error reading header: %d\n", ret); + goto unlock; + } + + if (buf[PAYLOAD_HEADER] != FRAME_START) { + dev_err(&client->dev, "invalid frame start: %d\n", buf[0]); + ret = -EIO; + goto unlock; + } + + if (buf[PAYLOAD_LENGTH] <= 0 || buf[PAYLOAD_LENGTH] > 255) { + dev_err(&client->dev, "invalid payload length: %d\n", + buf[PAYLOAD_LENGTH]); + ret = -EIO; + goto unlock; + } + + /* read the message */ + ret = i2c_master_recv(client, &buf[PAYLOAD_BODY], buf[PAYLOAD_LENGTH]); + if (ret < 0) { + dev_err(&client->dev, "error reading payload: %d\n", ret); + goto unlock; + } + + dev_dbg(&client->dev, "read %d bytes for response command 0x%x\n", + buf[PAYLOAD_LENGTH], buf[PAYLOAD_BODY]); + +unlock: + mutex_unlock(&ts->access_mutex); + return ret; +} + +static void zforce_complete(struct zforce_ts *ts, int cmd, int result) +{ + struct i2c_client *client = ts->client; + + if (ts->command_waiting == cmd) { + dev_dbg(&client->dev, "completing command 0x%x\n", cmd); + ts->command_result = result; + complete(&ts->command_done); + } else { + dev_dbg(&client->dev, "command %d not for us\n", cmd); + } +} + +static irqreturn_t zforce_interrupt(int irq, void *dev_id) +{ + struct zforce_ts *ts = dev_id; + struct i2c_client *client = ts->client; + const struct zforce_ts_platdata *pdata = dev_get_platdata(&client->dev); + int ret; + u8 payload_buffer[512]; + u8 *payload; + + /* + * When suspended, emit a wakeup signal if necessary and return. + * Due to the level-interrupt we will get re-triggered later. + */ + if (ts->suspended) { + if (device_may_wakeup(&client->dev)) + pm_wakeup_event(&client->dev, 500); + msleep(20); + return IRQ_HANDLED; + } + + dev_dbg(&client->dev, "handling interrupt\n"); + + /* Don't emit wakeup events from commands run by zforce_suspend */ + if (!ts->suspending && device_may_wakeup(&client->dev)) + pm_stay_awake(&client->dev); + + while (!gpio_get_value(pdata->gpio_int)) { + ret = zforce_read_packet(ts, payload_buffer); + if (ret < 0) { + dev_err(&client->dev, "could not read packet, ret: %d\n", + ret); + break; + } + + payload = &payload_buffer[PAYLOAD_BODY]; + + switch (payload[RESPONSE_ID]) { + case NOTIFICATION_TOUCH: + /* + * Always report touch-events received while + * suspending, when being a wakeup source + */ + if (ts->suspending && device_may_wakeup(&client->dev)) + pm_wakeup_event(&client->dev, 500); + zforce_touch_event(ts, &payload[RESPONSE_DATA]); + break; + + case NOTIFICATION_BOOTCOMPLETE: + ts->boot_complete = payload[RESPONSE_DATA]; + zforce_complete(ts, payload[RESPONSE_ID], 0); + break; + + case RESPONSE_INITIALIZE: + case RESPONSE_DEACTIVATE: + case RESPONSE_SETCONFIG: + case RESPONSE_RESOLUTION: + case RESPONSE_SCANFREQ: + zforce_complete(ts, payload[RESPONSE_ID], + payload[RESPONSE_DATA]); + break; + + case RESPONSE_STATUS: + /* + * Version Payload Results + * [2:major] [2:minor] [2:build] [2:rev] + */ + ts->version_major = (payload[RESPONSE_DATA + 1] << 8) | + payload[RESPONSE_DATA]; + ts->version_minor = (payload[RESPONSE_DATA + 3] << 8) | + payload[RESPONSE_DATA + 2]; + ts->version_build = (payload[RESPONSE_DATA + 5] << 8) | + payload[RESPONSE_DATA + 4]; + ts->version_rev = (payload[RESPONSE_DATA + 7] << 8) | + payload[RESPONSE_DATA + 6]; + dev_dbg(&ts->client->dev, "Firmware Version %04x:%04x %04x:%04x\n", + ts->version_major, ts->version_minor, + ts->version_build, ts->version_rev); + + zforce_complete(ts, payload[RESPONSE_ID], 0); + break; + + case NOTIFICATION_INVALID_COMMAND: + dev_err(&ts->client->dev, "invalid command: 0x%x\n", + payload[RESPONSE_DATA]); + break; + + default: + dev_err(&ts->client->dev, "unrecognized response id: 0x%x\n", + payload[RESPONSE_ID]); + break; + } + } + + if (!ts->suspending && device_may_wakeup(&client->dev)) + pm_relax(&client->dev); + + dev_dbg(&client->dev, "finished interrupt\n"); + + return IRQ_HANDLED; +} + +static int zforce_input_open(struct input_dev *dev) +{ + struct zforce_ts *ts = input_get_drvdata(dev); + int ret; + + ret = zforce_start(ts); + if (ret) + return ret; + + return 0; +} + +static void zforce_input_close(struct input_dev *dev) +{ + struct zforce_ts *ts = input_get_drvdata(dev); + struct i2c_client *client = ts->client; + int ret; + + ret = zforce_stop(ts); + if (ret) + dev_warn(&client->dev, "stopping zforce failed\n"); + + return; +} + +#ifdef CONFIG_PM_SLEEP +static int zforce_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct zforce_ts *ts = i2c_get_clientdata(client); + struct input_dev *input = ts->input; + int ret = 0; + + mutex_lock(&input->mutex); + ts->suspending = true; + + /* + * When configured as a wakeup source device should always wake + * the system, therefore start device if necessary. + */ + if (device_may_wakeup(&client->dev)) { + dev_dbg(&client->dev, "suspend while being a wakeup source\n"); + + /* Need to start device, if not open, to be a wakeup source. */ + if (!input->users) { + ret = zforce_start(ts); + if (ret) + goto unlock; + } + + enable_irq_wake(client->irq); + } else if (input->users) { + dev_dbg(&client->dev, "suspend without being a wakeup source\n"); + + ret = zforce_stop(ts); + if (ret) + goto unlock; + + disable_irq(client->irq); + } + + ts->suspended = true; + +unlock: + ts->suspending = false; + mutex_unlock(&input->mutex); + + return ret; +} + +static int zforce_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct zforce_ts *ts = i2c_get_clientdata(client); + struct input_dev *input = ts->input; + int ret = 0; + + mutex_lock(&input->mutex); + + ts->suspended = false; + + if (device_may_wakeup(&client->dev)) { + dev_dbg(&client->dev, "resume from being a wakeup source\n"); + + disable_irq_wake(client->irq); + + /* need to stop device if it was not open on suspend */ + if (!input->users) { + ret = zforce_stop(ts); + if (ret) + goto unlock; + } + } else if (input->users) { + dev_dbg(&client->dev, "resume without being a wakeup source\n"); + + enable_irq(client->irq); + + ret = zforce_start(ts); + if (ret < 0) + goto unlock; + } + +unlock: + mutex_unlock(&input->mutex); + + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(zforce_pm_ops, zforce_suspend, zforce_resume); + +static void zforce_reset(void *data) +{ + struct zforce_ts *ts = data; + + gpio_set_value(ts->pdata->gpio_rst, 0); +} + +static int zforce_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct zforce_ts_platdata *pdata = dev_get_platdata(&client->dev); + struct zforce_ts *ts; + struct input_dev *input_dev; + int ret; + + if (!pdata) + return -EINVAL; + + ts = devm_kzalloc(&client->dev, sizeof(struct zforce_ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + ret = devm_gpio_request_one(&client->dev, pdata->gpio_int, GPIOF_IN, + "zforce_ts_int"); + if (ret) { + dev_err(&client->dev, "request of gpio %d failed, %d\n", + pdata->gpio_int, ret); + return ret; + } + + ret = devm_gpio_request_one(&client->dev, pdata->gpio_rst, + GPIOF_OUT_INIT_LOW, "zforce_ts_rst"); + if (ret) { + dev_err(&client->dev, "request of gpio %d failed, %d\n", + pdata->gpio_rst, ret); + return ret; + } + + ret = devm_add_action(&client->dev, zforce_reset, ts); + if (ret) { + dev_err(&client->dev, "failed to register reset action, %d\n", + ret); + return ret; + } + + snprintf(ts->phys, sizeof(ts->phys), + "%s/input0", dev_name(&client->dev)); + + input_dev = devm_input_allocate_device(&client->dev); + if (!input_dev) { + dev_err(&client->dev, "could not allocate input device\n"); + return -ENOMEM; + } + + mutex_init(&ts->access_mutex); + mutex_init(&ts->command_mutex); + + ts->pdata = pdata; + ts->client = client; + ts->input = input_dev; + + input_dev->name = "Neonode zForce touchscreen"; + input_dev->phys = ts->phys; + input_dev->id.bustype = BUS_I2C; + + input_dev->open = zforce_input_open; + input_dev->close = zforce_input_close; + + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(EV_SYN, input_dev->evbit); + __set_bit(EV_ABS, input_dev->evbit); + + /* For multi touch */ + input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, + pdata->x_max, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, + pdata->y_max, 0, 0); + + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, + ZFORCE_MAX_AREA, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, + ZFORCE_MAX_AREA, 0, 0); + input_set_abs_params(input_dev, ABS_MT_ORIENTATION, 0, 1, 0, 0); + input_mt_init_slots(input_dev, ZFORCE_REPORT_POINTS, INPUT_MT_DIRECT); + + input_set_drvdata(ts->input, ts); + + init_completion(&ts->command_done); + + /* + * The zforce pulls the interrupt low when it has data ready. + * After it is triggered the isr thread runs until all the available + * packets have been read and the interrupt is high again. + * Therefore we can trigger the interrupt anytime it is low and do + * not need to limit it to the interrupt edge. + */ + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + zforce_interrupt, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + input_dev->name, ts); + if (ret) { + dev_err(&client->dev, "irq %d request failed\n", client->irq); + return ret; + } + + i2c_set_clientdata(client, ts); + + /* let the controller boot */ + gpio_set_value(pdata->gpio_rst, 1); + + ts->command_waiting = NOTIFICATION_BOOTCOMPLETE; + if (wait_for_completion_timeout(&ts->command_done, WAIT_TIMEOUT) == 0) + dev_warn(&client->dev, "bootcomplete timed out\n"); + + /* need to start device to get version information */ + ret = zforce_command_wait(ts, COMMAND_INITIALIZE); + if (ret) { + dev_err(&client->dev, "unable to initialize, %d\n", ret); + return ret; + } + + /* this gets the firmware version among other informations */ + ret = zforce_command_wait(ts, COMMAND_STATUS); + if (ret < 0) { + dev_err(&client->dev, "couldn't get status, %d\n", ret); + zforce_stop(ts); + return ret; + } + + /* stop device and put it into sleep until it is opened */ + ret = zforce_stop(ts); + if (ret < 0) + return ret; + + device_set_wakeup_capable(&client->dev, true); + + ret = input_register_device(input_dev); + if (ret) { + dev_err(&client->dev, "could not register input device, %d\n", + ret); + return ret; + } + + return 0; +} + +static struct i2c_device_id zforce_idtable[] = { + { "zforce-ts", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, zforce_idtable); + +static struct i2c_driver zforce_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "zforce-ts", + .pm = &zforce_pm_ops, + }, + .probe = zforce_probe, + .id_table = zforce_idtable, +}; + +module_i2c_driver(zforce_driver); + +MODULE_AUTHOR("Heiko Stuebner "); +MODULE_DESCRIPTION("zForce TouchScreen Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/zforce_ts.h b/include/linux/platform_data/zforce_ts.h new file mode 100644 index 000000000000..0472ab2f6ede --- /dev/null +++ b/include/linux/platform_data/zforce_ts.h @@ -0,0 +1,26 @@ +/* drivers/input/touchscreen/zforce.c + * + * Copyright (C) 2012-2013 MundoReader S.L. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _LINUX_INPUT_ZFORCE_TS_H +#define _LINUX_INPUT_ZFORCE_TS_H + +struct zforce_ts_platdata { + int gpio_int; + int gpio_rst; + + unsigned int x_max; + unsigned int y_max; +}; + +#endif /* _LINUX_INPUT_ZFORCE_TS_H */ -- cgit v1.2.3 From ac65caf514ec3e55e8d3d510ee37f80dd97418fe Mon Sep 17 00:00:00 2001 From: Pekon Gupta Date: Thu, 24 Oct 2013 18:20:17 +0530 Subject: ARM: OMAP2+: cleaned-up DT support of various ECC schemes OMAP NAND driver support multiple ECC scheme, which can used in different flavours, depending on in-build Hardware engines present on SoC. This patch updates following in DT bindings related to sectionion of ecc-schemes - ti,elm-id: replaces elm_id (maintains backward compatibility) - ti,nand-ecc-opts: selection of h/w or s/w implementation of an ecc-scheme depends on ti,elm-id. (supported values ham1, bch4, and bch8) - maintain backward compatibility to deprecated DT bindings (sw, hw, hw-romcode) Below table shows different flavours of ecc-schemes supported by OMAP devices +---------------------------------------+---------------+---------------+ | ECC scheme |ECC calculation|Error detection| +---------------------------------------+---------------+---------------+ |OMAP_ECC_HAM1_CODE_HW |H/W (GPMC) |S/W | +---------------------------------------+---------------+---------------+ |OMAP_ECC_BCH8_CODE_HW_DETECTION_SW |H/W (GPMC) |S/W | |(requires CONFIG_MTD_NAND_ECC_BCH) | | | +---------------------------------------+---------------+---------------+ |OMAP_ECC_BCH8_CODE_HW |H/W (GPMC) |H/W (ELM) | |(requires CONFIG_MTD_NAND_OMAP_BCH && | | | | ti,elm-id in DT) | | | +---------------------------------------+---------------+---------------+ To optimize footprint of omap2-nand driver, selection of some ECC schemes also require enabling following Kconfigs, in addition to setting appropriate DT bindings - Kconfig:CONFIG_MTD_NAND_ECC_BCH error detection done in software - Kconfig:CONFIG_MTD_NAND_OMAP_BCH error detection done by h/w engine Signed-off-by: Pekon Gupta Reviewed-by: Felipe Balbi Acked-by: Tony Lindgren Tested-by: Ezequiel Garcia Signed-off-by: Brian Norris --- .../devicetree/bindings/mtd/gpmc-nand.txt | 8 +++- arch/arm/mach-omap2/gpmc.c | 48 +++++++++++++++------- include/linux/platform_data/mtd-nand-omap2.h | 13 +++++- 3 files changed, 51 insertions(+), 18 deletions(-) (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/mtd/gpmc-nand.txt b/Documentation/devicetree/bindings/mtd/gpmc-nand.txt index df338cb5059c..bfe07e152738 100644 --- a/Documentation/devicetree/bindings/mtd/gpmc-nand.txt +++ b/Documentation/devicetree/bindings/mtd/gpmc-nand.txt @@ -36,8 +36,12 @@ Optional properties: "prefetch-dma" Prefetch enabled sDMA mode "prefetch-irq" Prefetch enabled irq mode - - elm_id: Specifies elm device node. This is required to support BCH - error correction using ELM module. + - elm_id: use "ti,elm-id" instead + - ti,elm-id: Specifies phandle of the ELM devicetree node. + ELM is an on-chip hardware engine on TI SoC which is used for + locating ECC errors for BCHx algorithms. SoC devices which have + ELM hardware engines should specify this device node in .dtsi + Using ELM for ECC error correction frees some CPU cycles. For inline partiton table parsing (optional): diff --git a/arch/arm/mach-omap2/gpmc.c b/arch/arm/mach-omap2/gpmc.c index 579697adaae7..c877129142ba 100644 --- a/arch/arm/mach-omap2/gpmc.c +++ b/arch/arm/mach-omap2/gpmc.c @@ -1341,14 +1341,6 @@ static void __maybe_unused gpmc_read_timings_dt(struct device_node *np, #ifdef CONFIG_MTD_NAND -static const char * const nand_ecc_opts[] = { - [OMAP_ECC_HAMMING_CODE_DEFAULT] = "sw", - [OMAP_ECC_HAMMING_CODE_HW] = "hw", - [OMAP_ECC_HAMMING_CODE_HW_ROMCODE] = "hw-romcode", - [OMAP_ECC_BCH4_CODE_HW] = "bch4", - [OMAP_ECC_BCH8_CODE_HW] = "bch8", -}; - static const char * const nand_xfer_types[] = { [NAND_OMAP_PREFETCH_POLLED] = "prefetch-polled", [NAND_OMAP_POLLED] = "polled", @@ -1378,13 +1370,41 @@ static int gpmc_probe_nand_child(struct platform_device *pdev, gpmc_nand_data->cs = val; gpmc_nand_data->of_node = child; - if (!of_property_read_string(child, "ti,nand-ecc-opt", &s)) - for (val = 0; val < ARRAY_SIZE(nand_ecc_opts); val++) - if (!strcasecmp(s, nand_ecc_opts[val])) { - gpmc_nand_data->ecc_opt = val; - break; - } + /* Detect availability of ELM module */ + gpmc_nand_data->elm_of_node = of_parse_phandle(child, "ti,elm-id", 0); + if (gpmc_nand_data->elm_of_node == NULL) + gpmc_nand_data->elm_of_node = + of_parse_phandle(child, "elm_id", 0); + if (gpmc_nand_data->elm_of_node == NULL) + pr_warn("%s: ti,elm-id property not found\n", __func__); + + /* select ecc-scheme for NAND */ + if (of_property_read_string(child, "ti,nand-ecc-opt", &s)) { + pr_err("%s: ti,nand-ecc-opt not found\n", __func__); + return -ENODEV; + } + if (!strcmp(s, "ham1") || !strcmp(s, "sw") || + !strcmp(s, "hw") || !strcmp(s, "hw-romcode")) + gpmc_nand_data->ecc_opt = + OMAP_ECC_HAM1_CODE_HW; + else if (!strcmp(s, "bch4")) + if (gpmc_nand_data->elm_of_node) + gpmc_nand_data->ecc_opt = + OMAP_ECC_BCH4_CODE_HW; + else + gpmc_nand_data->ecc_opt = + OMAP_ECC_BCH4_CODE_HW_DETECTION_SW; + else if (!strcmp(s, "bch8")) + if (gpmc_nand_data->elm_of_node) + gpmc_nand_data->ecc_opt = + OMAP_ECC_BCH8_CODE_HW; + else + gpmc_nand_data->ecc_opt = + OMAP_ECC_BCH8_CODE_HW_DETECTION_SW; + else + pr_err("%s: ti,nand-ecc-opt invalid value\n", __func__); + /* select data transfer mode for NAND controller */ if (!of_property_read_string(child, "ti,nand-xfer-type", &s)) for (val = 0; val < ARRAY_SIZE(nand_xfer_types); val++) if (!strcasecmp(s, nand_xfer_types[val])) { diff --git a/include/linux/platform_data/mtd-nand-omap2.h b/include/linux/platform_data/mtd-nand-omap2.h index 6bf9ef43ddb1..e4128f1510bf 100644 --- a/include/linux/platform_data/mtd-nand-omap2.h +++ b/include/linux/platform_data/mtd-nand-omap2.h @@ -28,8 +28,16 @@ enum omap_ecc { OMAP_ECC_HAMMING_CODE_HW, /* gpmc to detect the error */ /* 1-bit ecc: stored at beginning of spare area as romcode */ OMAP_ECC_HAMMING_CODE_HW_ROMCODE, /* gpmc method & romcode layout */ - OMAP_ECC_BCH4_CODE_HW, /* 4-bit BCH ecc code */ - OMAP_ECC_BCH8_CODE_HW, /* 8-bit BCH ecc code */ + /* 1-bit ECC calculation by GPMC, Error detection by Software */ + OMAP_ECC_HAM1_CODE_HW, + /* 4-bit ECC calculation by GPMC, Error detection by Software */ + OMAP_ECC_BCH4_CODE_HW_DETECTION_SW, + /* 4-bit ECC calculation by GPMC, Error detection by ELM */ + OMAP_ECC_BCH4_CODE_HW, + /* 8-bit ECC calculation by GPMC, Error detection by Software */ + OMAP_ECC_BCH8_CODE_HW_DETECTION_SW, + /* 8-bit ECC calculation by GPMC, Error detection by ELM */ + OMAP_ECC_BCH8_CODE_HW, }; struct gpmc_nand_regs { @@ -63,5 +71,6 @@ struct omap_nand_platform_data { /* for passing the partitions */ struct device_node *of_node; + struct device_node *elm_of_node; }; #endif -- cgit v1.2.3 From c66d039197e42af8867e5d0d9b904daf0fb9e6bc Mon Sep 17 00:00:00 2001 From: Pekon Gupta Date: Thu, 24 Oct 2013 18:20:18 +0530 Subject: mtd: nand: omap: combine different flavours of 1-bit hamming ecc schemes OMAP NAND driver currently supports multiple flavours of 1-bit Hamming ecc-scheme, like: - OMAP_ECC_HAMMING_CODE_DEFAULT 1-bit hamming ecc code using software library - OMAP_ECC_HAMMING_CODE_HW 1-bit hamming ecc-code using GPMC h/w engine - OMAP_ECC_HAMMING_CODE_HW_ROMCODE 1-bit hamming ecc-code using GPMC h/w engin with ecc-layout compatible to ROM code. This patch combines above multiple ecc-schemes into single implementation: - OMAP_ECC_HAM1_CODE_HW 1-bit hamming ecc-code using GPMC h/w engine with ROM-code compatible ecc-layout. Signed-off-by: Pekon Gupta Reviewed-by: Felipe Balbi Acked-by: Tony Lindgren Tested-by: Ezequiel Garcia Signed-off-by: Brian Norris --- Documentation/devicetree/bindings/mtd/gpmc-nand.txt | 8 ++++---- arch/arm/mach-omap2/board-flash.c | 2 +- drivers/mtd/nand/omap2.c | 9 +++------ include/linux/platform_data/mtd-nand-omap2.h | 7 +------ 4 files changed, 9 insertions(+), 17 deletions(-) (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/mtd/gpmc-nand.txt b/Documentation/devicetree/bindings/mtd/gpmc-nand.txt index bfe07e152738..5e1f31b5ff70 100644 --- a/Documentation/devicetree/bindings/mtd/gpmc-nand.txt +++ b/Documentation/devicetree/bindings/mtd/gpmc-nand.txt @@ -22,10 +22,10 @@ Optional properties: width of 8 is assumed. - ti,nand-ecc-opt: A string setting the ECC layout to use. One of: - - "sw" Software method (default) - "hw" Hardware method - "hw-romcode" gpmc hamming mode method & romcode layout + "sw" use "ham1" instead + "hw" use "ham1" instead + "hw-romcode" use "ham1" instead + "ham1" 1-bit Hamming ecc code "bch4" 4-bit BCH ecc code "bch8" 8-bit BCH ecc code diff --git a/arch/arm/mach-omap2/board-flash.c b/arch/arm/mach-omap2/board-flash.c index fc20a61f6b2a..ac82512b9c8c 100644 --- a/arch/arm/mach-omap2/board-flash.c +++ b/arch/arm/mach-omap2/board-flash.c @@ -142,7 +142,7 @@ __init board_nand_init(struct mtd_partition *nand_parts, u8 nr_parts, u8 cs, board_nand_data.nr_parts = nr_parts; board_nand_data.devsize = nand_type; - board_nand_data.ecc_opt = OMAP_ECC_HAMMING_CODE_DEFAULT; + board_nand_data.ecc_opt = OMAP_ECC_BCH8_CODE_HW; gpmc_nand_init(&board_nand_data, gpmc_t); } #endif /* CONFIG_MTD_NAND_OMAP2 || CONFIG_MTD_NAND_OMAP2_MODULE */ diff --git a/drivers/mtd/nand/omap2.c b/drivers/mtd/nand/omap2.c index 4ecf0e5fd484..8d521aa001c8 100644 --- a/drivers/mtd/nand/omap2.c +++ b/drivers/mtd/nand/omap2.c @@ -1993,10 +1993,7 @@ static int omap_nand_probe(struct platform_device *pdev) } /* select the ecc type */ - if (pdata->ecc_opt == OMAP_ECC_HAMMING_CODE_DEFAULT) - info->nand.ecc.mode = NAND_ECC_SOFT; - else if ((pdata->ecc_opt == OMAP_ECC_HAMMING_CODE_HW) || - (pdata->ecc_opt == OMAP_ECC_HAMMING_CODE_HW_ROMCODE)) { + if (pdata->ecc_opt == OMAP_ECC_HAM1_CODE_HW) { info->nand.ecc.bytes = 3; info->nand.ecc.size = 512; info->nand.ecc.strength = 1; @@ -2025,7 +2022,7 @@ static int omap_nand_probe(struct platform_device *pdev) } /* rom code layout */ - if (pdata->ecc_opt == OMAP_ECC_HAMMING_CODE_HW_ROMCODE) { + if (pdata->ecc_opt == OMAP_ECC_HAM1_CODE_HW) { if (info->nand.options & NAND_BUSWIDTH_16) offset = 2; @@ -2033,7 +2030,7 @@ static int omap_nand_probe(struct platform_device *pdev) offset = 1; info->nand.badblock_pattern = &bb_descrip_flashbased; } - omap_oobinfo.eccbytes = 3 * (info->mtd.oobsize/16); + omap_oobinfo.eccbytes = 3 * (info->mtd.writesize / 512); for (i = 0; i < omap_oobinfo.eccbytes; i++) omap_oobinfo.eccpos[i] = i+offset; diff --git a/include/linux/platform_data/mtd-nand-omap2.h b/include/linux/platform_data/mtd-nand-omap2.h index e4128f1510bf..4da5bfa2147f 100644 --- a/include/linux/platform_data/mtd-nand-omap2.h +++ b/include/linux/platform_data/mtd-nand-omap2.h @@ -23,13 +23,8 @@ enum nand_io { }; enum omap_ecc { - /* 1-bit ecc: stored at end of spare area */ - OMAP_ECC_HAMMING_CODE_DEFAULT = 0, /* Default, s/w method */ - OMAP_ECC_HAMMING_CODE_HW, /* gpmc to detect the error */ - /* 1-bit ecc: stored at beginning of spare area as romcode */ - OMAP_ECC_HAMMING_CODE_HW_ROMCODE, /* gpmc method & romcode layout */ /* 1-bit ECC calculation by GPMC, Error detection by Software */ - OMAP_ECC_HAM1_CODE_HW, + OMAP_ECC_HAM1_CODE_HW = 0, /* 4-bit ECC calculation by GPMC, Error detection by Software */ OMAP_ECC_BCH4_CODE_HW_DETECTION_SW, /* 4-bit ECC calculation by GPMC, Error detection by ELM */ -- cgit v1.2.3 From 5812c13a4e636da4bd7f7cabbbbc59d9dbf3c86c Mon Sep 17 00:00:00 2001 From: Milo Kim Date: Tue, 12 Nov 2013 15:08:57 -0800 Subject: backlight: lp855x_bl: support new LP8555 device LP8555 is one of the LP855x family devices. This device needs pre_init_device() and post_init_device() driver structure. It's same as LP8557, so the device configuration code is shared with LP8557. Backlight outputs are generated from dual DC-DC boost converters. It's configurable EPROM settings which are defined in the platform data. Driver documentation and device tree bindings are updated. Signed-off-by: Milo Kim Signed-off-by: Jingoo Han Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- Documentation/backlight/lp855x-driver.txt | 5 ++-- .../devicetree/bindings/video/backlight/lp855x.txt | 29 +++++++++++++++++++++- drivers/video/backlight/Kconfig | 4 +-- drivers/video/backlight/lp855x_bl.c | 17 +++++++++++-- include/linux/platform_data/lp855x.h | 19 ++++++++++++++ 5 files changed, 67 insertions(+), 7 deletions(-) (limited to 'include/linux/platform_data') diff --git a/Documentation/backlight/lp855x-driver.txt b/Documentation/backlight/lp855x-driver.txt index 1c732f0c6758..01bce243d3d7 100644 --- a/Documentation/backlight/lp855x-driver.txt +++ b/Documentation/backlight/lp855x-driver.txt @@ -4,7 +4,8 @@ Kernel driver lp855x Backlight driver for LP855x ICs Supported chips: - Texas Instruments LP8550, LP8551, LP8552, LP8553, LP8556 and LP8557 + Texas Instruments LP8550, LP8551, LP8552, LP8553, LP8555, LP8556 and + LP8557 Author: Milo(Woogyom) Kim @@ -24,7 +25,7 @@ Value : pwm based or register based 2) chip_id The lp855x chip id. -Value : lp8550/lp8551/lp8552/lp8553/lp8556/lp8557 +Value : lp8550/lp8551/lp8552/lp8553/lp8555/lp8556/lp8557 Platform data for lp855x ------------------------ diff --git a/Documentation/devicetree/bindings/video/backlight/lp855x.txt b/Documentation/devicetree/bindings/video/backlight/lp855x.txt index 1482103d288f..96e83a56048e 100644 --- a/Documentation/devicetree/bindings/video/backlight/lp855x.txt +++ b/Documentation/devicetree/bindings/video/backlight/lp855x.txt @@ -2,7 +2,7 @@ lp855x bindings Required properties: - compatible: "ti,lp8550", "ti,lp8551", "ti,lp8552", "ti,lp8553", - "ti,lp8556", "ti,lp8557" + "ti,lp8555", "ti,lp8556", "ti,lp8557" - reg: I2C slave address (u8) - dev-ctrl: Value of DEVICE CONTROL register (u8). It depends on the device. @@ -15,6 +15,33 @@ Optional properties: Example: + /* LP8555 */ + backlight@2c { + compatible = "ti,lp8555"; + reg = <0x2c>; + + dev-ctrl = /bits/ 8 <0x00>; + pwm-period = <10000>; + + /* 4V OV, 4 output LED0 string enabled */ + rom_14h { + rom-addr = /bits/ 8 <0x14>; + rom-val = /bits/ 8 <0xcf>; + }; + + /* Heavy smoothing, 24ms ramp time step */ + rom_15h { + rom-addr = /bits/ 8 <0x15>; + rom-val = /bits/ 8 <0xc7>; + }; + + /* 4 output LED1 string enabled */ + rom_19h { + rom-addr = /bits/ 8 <0x19>; + rom-val = /bits/ 8 <0x0f>; + }; + }; + /* LP8556 */ backlight@2c { compatible = "ti,lp8556"; diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index d4a7a351d67c..a65dd063ecad 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -388,8 +388,8 @@ config BACKLIGHT_LP855X tristate "Backlight driver for TI LP855X" depends on BACKLIGHT_CLASS_DEVICE && I2C help - This supports TI LP8550, LP8551, LP8552, LP8553, LP8556 and LP8557 - backlight driver. + This supports TI LP8550, LP8551, LP8552, LP8553, LP8555, LP8556 and + LP8557 backlight driver. config BACKLIGHT_LP8788 tristate "Backlight driver for TI LP8788 MFD" diff --git a/drivers/video/backlight/lp855x_bl.c b/drivers/video/backlight/lp855x_bl.c index c0b41f13bd4a..c952175d4113 100644 --- a/drivers/video/backlight/lp855x_bl.c +++ b/drivers/video/backlight/lp855x_bl.c @@ -26,13 +26,15 @@ #define LP8556_EPROM_START 0xA0 #define LP8556_EPROM_END 0xAF -/* LP8557 Registers */ +/* LP8555/7 Registers */ #define LP8557_BL_CMD 0x00 #define LP8557_BL_MASK 0x01 #define LP8557_BL_ON 0x01 #define LP8557_BL_OFF 0x00 #define LP8557_BRIGHTNESS_CTRL 0x04 #define LP8557_CONFIG 0x10 +#define LP8555_EPROM_START 0x10 +#define LP8555_EPROM_END 0x7A #define LP8557_EPROM_START 0x10 #define LP8557_EPROM_END 0x1E @@ -111,6 +113,10 @@ static bool lp855x_is_valid_rom_area(struct lp855x *lp, u8 addr) start = LP8556_EPROM_START; end = LP8556_EPROM_END; break; + case LP8555: + start = LP8555_EPROM_START; + end = LP8555_EPROM_END; + break; case LP8557: start = LP8557_EPROM_START; end = LP8557_EPROM_END; @@ -165,9 +171,14 @@ static int lp855x_configure(struct lp855x *lp) struct lp855x_platform_data *pd = lp->pdata; switch (lp->chip_id) { - case LP8550 ... LP8556: + case LP8550: + case LP8551: + case LP8552: + case LP8553: + case LP8556: lp->cfg = &lp855x_dev_cfg; break; + case LP8555: case LP8557: lp->cfg = &lp8557_dev_cfg; break; @@ -470,6 +481,7 @@ static const struct of_device_id lp855x_dt_ids[] = { { .compatible = "ti,lp8551", }, { .compatible = "ti,lp8552", }, { .compatible = "ti,lp8553", }, + { .compatible = "ti,lp8555", }, { .compatible = "ti,lp8556", }, { .compatible = "ti,lp8557", }, { } @@ -481,6 +493,7 @@ static const struct i2c_device_id lp855x_ids[] = { {"lp8551", LP8551}, {"lp8552", LP8552}, {"lp8553", LP8553}, + {"lp8555", LP8555}, {"lp8556", LP8556}, {"lp8557", LP8557}, { } diff --git a/include/linux/platform_data/lp855x.h b/include/linux/platform_data/lp855x.h index ea3200527dd3..1b2ba24e4e03 100644 --- a/include/linux/platform_data/lp855x.h +++ b/include/linux/platform_data/lp855x.h @@ -40,6 +40,17 @@ #define LP8553_PWM_CONFIG LP8550_PWM_CONFIG #define LP8553_I2C_CONFIG LP8550_I2C_CONFIG +/* CONFIG register - LP8555 */ +#define LP8555_PWM_STANDBY BIT(7) +#define LP8555_PWM_FILTER BIT(6) +#define LP8555_RELOAD_EPROM BIT(3) /* use it if EPROMs should be reset + when the backlight turns on */ +#define LP8555_OFF_OPENLEDS BIT(2) +#define LP8555_PWM_CONFIG LP8555_PWM_ONLY +#define LP8555_I2C_CONFIG LP8555_I2C_ONLY +#define LP8555_COMB1_CONFIG LP8555_COMBINED1 +#define LP8555_COMB2_CONFIG LP8555_COMBINED2 + /* DEVICE CONTROL register - LP8556 */ #define LP8556_PWM_CONFIG (LP8556_PWM_ONLY << BRT_MODE_SHFT) #define LP8556_COMB1_CONFIG (LP8556_COMBINED1 << BRT_MODE_SHFT) @@ -65,6 +76,7 @@ enum lp855x_chip_id { LP8551, LP8552, LP8553, + LP8555, LP8556, LP8557, }; @@ -89,6 +101,13 @@ enum lp8553_brighntess_source { LP8553_I2C_ONLY = LP8550_I2C_ONLY, }; +enum lp8555_brightness_source { + LP8555_PWM_ONLY, + LP8555_I2C_ONLY, + LP8555_COMBINED1, /* Brightness register with shaped PWM */ + LP8555_COMBINED2, /* PWM with shaped brightness register */ +}; + enum lp8556_brightness_source { LP8556_PWM_ONLY, LP8556_COMBINED1, /* pwm + i2c before the shaper block */ -- cgit v1.2.3 From 28e64a68a2ef1c48f30e8b6803725199929069fc Mon Sep 17 00:00:00 2001 From: Daniel Jeong Date: Tue, 12 Nov 2013 15:08:58 -0800 Subject: backlight: lm3630: apply chip revision The LM3630 chip was revised by TI and chip name was also changed to LM3630A. And register map, default values and initial sequences are changed. The files, lm3630_bl.{c,h} are replaced by lm3630a_bl.{c,h} You can find more information about LM3630A(datasheet, evm etc) at http://www.ti.com/product/lm3630a Signed-off-by: Daniel Jeong Signed-off-by: Jingoo Han Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/video/backlight/Kconfig | 6 +- drivers/video/backlight/Makefile | 2 +- drivers/video/backlight/lm3630_bl.c | 475 ------------------------------ drivers/video/backlight/lm3630a_bl.c | 483 +++++++++++++++++++++++++++++++ include/linux/platform_data/lm3630_bl.h | 57 ---- include/linux/platform_data/lm3630a_bl.h | 65 +++++ 6 files changed, 552 insertions(+), 536 deletions(-) delete mode 100644 drivers/video/backlight/lm3630_bl.c create mode 100644 drivers/video/backlight/lm3630a_bl.c delete mode 100644 include/linux/platform_data/lm3630_bl.h create mode 100644 include/linux/platform_data/lm3630a_bl.h (limited to 'include/linux/platform_data') diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index a65dd063ecad..5a3eb2ecb525 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -368,12 +368,12 @@ config BACKLIGHT_AAT2870 If you have a AnalogicTech AAT2870 say Y to enable the backlight driver. -config BACKLIGHT_LM3630 - tristate "Backlight Driver for LM3630" +config BACKLIGHT_LM3630A + tristate "Backlight Driver for LM3630A" depends on BACKLIGHT_CLASS_DEVICE && I2C select REGMAP_I2C help - This supports TI LM3630 Backlight Driver + This supports TI LM3630A Backlight Driver config BACKLIGHT_LM3639 tristate "Backlight Driver for LM3639" diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 38e1babb1946..bb820024f346 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -37,7 +37,7 @@ obj-$(CONFIG_BACKLIGHT_GPIO) += gpio_backlight.o obj-$(CONFIG_BACKLIGHT_HP680) += hp680_bl.o obj-$(CONFIG_BACKLIGHT_HP700) += jornada720_bl.o obj-$(CONFIG_BACKLIGHT_LM3533) += lm3533_bl.o -obj-$(CONFIG_BACKLIGHT_LM3630) += lm3630_bl.o +obj-$(CONFIG_BACKLIGHT_LM3630A) += lm3630a_bl.o obj-$(CONFIG_BACKLIGHT_LM3639) += lm3639_bl.o obj-$(CONFIG_BACKLIGHT_LOCOMO) += locomolcd.o obj-$(CONFIG_BACKLIGHT_LP855X) += lp855x_bl.o diff --git a/drivers/video/backlight/lm3630_bl.c b/drivers/video/backlight/lm3630_bl.c deleted file mode 100644 index 76a62e978fc3..000000000000 --- a/drivers/video/backlight/lm3630_bl.c +++ /dev/null @@ -1,475 +0,0 @@ -/* -* Simple driver for Texas Instruments LM3630 Backlight driver chip -* Copyright (C) 2012 Texas Instruments -* -* 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define REG_CTRL 0x00 -#define REG_CONFIG 0x01 -#define REG_BRT_A 0x03 -#define REG_BRT_B 0x04 -#define REG_INT_STATUS 0x09 -#define REG_INT_EN 0x0A -#define REG_FAULT 0x0B -#define REG_PWM_OUTLOW 0x12 -#define REG_PWM_OUTHIGH 0x13 -#define REG_MAX 0x1F - -#define INT_DEBOUNCE_MSEC 10 - -enum lm3630_leds { - BLED_ALL = 0, - BLED_1, - BLED_2 -}; - -static const char * const bled_name[] = { - [BLED_ALL] = "lm3630_bled", /*Bank1 controls all string */ - [BLED_1] = "lm3630_bled1", /*Bank1 controls bled1 */ - [BLED_2] = "lm3630_bled2", /*Bank1 or 2 controls bled2 */ -}; - -struct lm3630_chip_data { - struct device *dev; - struct delayed_work work; - int irq; - struct workqueue_struct *irqthread; - struct lm3630_platform_data *pdata; - struct backlight_device *bled1; - struct backlight_device *bled2; - struct regmap *regmap; -}; - -/* initialize chip */ -static int lm3630_chip_init(struct lm3630_chip_data *pchip) -{ - int ret; - unsigned int reg_val; - struct lm3630_platform_data *pdata = pchip->pdata; - - /*pwm control */ - reg_val = ((pdata->pwm_active & 0x01) << 2) | (pdata->pwm_ctrl & 0x03); - ret = regmap_update_bits(pchip->regmap, REG_CONFIG, 0x07, reg_val); - if (ret < 0) - goto out; - - /* bank control */ - reg_val = ((pdata->bank_b_ctrl & 0x01) << 1) | - (pdata->bank_a_ctrl & 0x07); - ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x07, reg_val); - if (ret < 0) - goto out; - - ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x80, 0x00); - if (ret < 0) - goto out; - - /* set initial brightness */ - if (pdata->bank_a_ctrl != BANK_A_CTRL_DISABLE) { - ret = regmap_write(pchip->regmap, - REG_BRT_A, pdata->init_brt_led1); - if (ret < 0) - goto out; - } - - if (pdata->bank_b_ctrl != BANK_B_CTRL_DISABLE) { - ret = regmap_write(pchip->regmap, - REG_BRT_B, pdata->init_brt_led2); - if (ret < 0) - goto out; - } - return ret; - -out: - dev_err(pchip->dev, "i2c failed to access register\n"); - return ret; -} - -/* interrupt handling */ -static void lm3630_delayed_func(struct work_struct *work) -{ - int ret; - unsigned int reg_val; - struct lm3630_chip_data *pchip; - - pchip = container_of(work, struct lm3630_chip_data, work.work); - - ret = regmap_read(pchip->regmap, REG_INT_STATUS, ®_val); - if (ret < 0) { - dev_err(pchip->dev, - "i2c failed to access REG_INT_STATUS Register\n"); - return; - } - - dev_info(pchip->dev, "REG_INT_STATUS Register is 0x%x\n", reg_val); -} - -static irqreturn_t lm3630_isr_func(int irq, void *chip) -{ - int ret; - struct lm3630_chip_data *pchip = chip; - unsigned long delay = msecs_to_jiffies(INT_DEBOUNCE_MSEC); - - queue_delayed_work(pchip->irqthread, &pchip->work, delay); - - ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x80, 0x00); - if (ret < 0) - goto out; - - return IRQ_HANDLED; -out: - dev_err(pchip->dev, "i2c failed to access register\n"); - return IRQ_HANDLED; -} - -static int lm3630_intr_config(struct lm3630_chip_data *pchip) -{ - INIT_DELAYED_WORK(&pchip->work, lm3630_delayed_func); - pchip->irqthread = create_singlethread_workqueue("lm3630-irqthd"); - if (!pchip->irqthread) { - dev_err(pchip->dev, "create irq thread fail...\n"); - return -1; - } - if (request_threaded_irq - (pchip->irq, NULL, lm3630_isr_func, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "lm3630_irq", pchip)) { - dev_err(pchip->dev, "request threaded irq fail..\n"); - return -1; - } - return 0; -} - -static bool -set_intensity(struct backlight_device *bl, struct lm3630_chip_data *pchip) -{ - if (!pchip->pdata->pwm_set_intensity) - return false; - pchip->pdata->pwm_set_intensity(bl->props.brightness - 1, - pchip->pdata->pwm_period); - return true; -} - -/* update and get brightness */ -static int lm3630_bank_a_update_status(struct backlight_device *bl) -{ - int ret; - struct lm3630_chip_data *pchip = bl_get_data(bl); - enum lm3630_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl; - - /* brightness 0 means disable */ - if (!bl->props.brightness) { - ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x04, 0x00); - if (ret < 0) - goto out; - return bl->props.brightness; - } - - /* pwm control */ - if (pwm_ctrl == PWM_CTRL_BANK_A || pwm_ctrl == PWM_CTRL_BANK_ALL) { - if (!set_intensity(bl, pchip)) - dev_err(pchip->dev, "No pwm control func. in plat-data\n"); - } else { - - /* i2c control */ - ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x80, 0x00); - if (ret < 0) - goto out; - mdelay(1); - ret = regmap_write(pchip->regmap, - REG_BRT_A, bl->props.brightness - 1); - if (ret < 0) - goto out; - } - return bl->props.brightness; -out: - dev_err(pchip->dev, "i2c failed to access REG_CTRL\n"); - return bl->props.brightness; -} - -static int lm3630_bank_a_get_brightness(struct backlight_device *bl) -{ - unsigned int reg_val; - int brightness, ret; - struct lm3630_chip_data *pchip = bl_get_data(bl); - enum lm3630_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl; - - if (pwm_ctrl == PWM_CTRL_BANK_A || pwm_ctrl == PWM_CTRL_BANK_ALL) { - ret = regmap_read(pchip->regmap, REG_PWM_OUTHIGH, ®_val); - if (ret < 0) - goto out; - brightness = reg_val & 0x01; - ret = regmap_read(pchip->regmap, REG_PWM_OUTLOW, ®_val); - if (ret < 0) - goto out; - brightness = ((brightness << 8) | reg_val) + 1; - } else { - ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x80, 0x00); - if (ret < 0) - goto out; - mdelay(1); - ret = regmap_read(pchip->regmap, REG_BRT_A, ®_val); - if (ret < 0) - goto out; - brightness = reg_val + 1; - } - bl->props.brightness = brightness; - return bl->props.brightness; -out: - dev_err(pchip->dev, "i2c failed to access register\n"); - return 0; -} - -static const struct backlight_ops lm3630_bank_a_ops = { - .options = BL_CORE_SUSPENDRESUME, - .update_status = lm3630_bank_a_update_status, - .get_brightness = lm3630_bank_a_get_brightness, -}; - -static int lm3630_bank_b_update_status(struct backlight_device *bl) -{ - int ret; - struct lm3630_chip_data *pchip = bl_get_data(bl); - enum lm3630_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl; - - if (pwm_ctrl == PWM_CTRL_BANK_B || pwm_ctrl == PWM_CTRL_BANK_ALL) { - if (!set_intensity(bl, pchip)) - dev_err(pchip->dev, - "no pwm control func. in plat-data\n"); - } else { - ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x80, 0x00); - if (ret < 0) - goto out; - mdelay(1); - ret = regmap_write(pchip->regmap, - REG_BRT_B, bl->props.brightness - 1); - } - return bl->props.brightness; -out: - dev_err(pchip->dev, "i2c failed to access register\n"); - return bl->props.brightness; -} - -static int lm3630_bank_b_get_brightness(struct backlight_device *bl) -{ - unsigned int reg_val; - int brightness, ret; - struct lm3630_chip_data *pchip = bl_get_data(bl); - enum lm3630_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl; - - if (pwm_ctrl == PWM_CTRL_BANK_B || pwm_ctrl == PWM_CTRL_BANK_ALL) { - ret = regmap_read(pchip->regmap, REG_PWM_OUTHIGH, ®_val); - if (ret < 0) - goto out; - brightness = reg_val & 0x01; - ret = regmap_read(pchip->regmap, REG_PWM_OUTLOW, ®_val); - if (ret < 0) - goto out; - brightness = ((brightness << 8) | reg_val) + 1; - } else { - ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x80, 0x00); - if (ret < 0) - goto out; - mdelay(1); - ret = regmap_read(pchip->regmap, REG_BRT_B, ®_val); - if (ret < 0) - goto out; - brightness = reg_val + 1; - } - bl->props.brightness = brightness; - - return bl->props.brightness; -out: - dev_err(pchip->dev, "i2c failed to access register\n"); - return bl->props.brightness; -} - -static const struct backlight_ops lm3630_bank_b_ops = { - .options = BL_CORE_SUSPENDRESUME, - .update_status = lm3630_bank_b_update_status, - .get_brightness = lm3630_bank_b_get_brightness, -}; - -static int lm3630_backlight_register(struct lm3630_chip_data *pchip, - enum lm3630_leds ledno) -{ - const char *name = bled_name[ledno]; - struct backlight_properties props; - struct lm3630_platform_data *pdata = pchip->pdata; - - props.type = BACKLIGHT_RAW; - switch (ledno) { - case BLED_1: - case BLED_ALL: - props.brightness = pdata->init_brt_led1; - props.max_brightness = pdata->max_brt_led1; - pchip->bled1 = - backlight_device_register(name, pchip->dev, pchip, - &lm3630_bank_a_ops, &props); - if (IS_ERR(pchip->bled1)) - return PTR_ERR(pchip->bled1); - break; - case BLED_2: - props.brightness = pdata->init_brt_led2; - props.max_brightness = pdata->max_brt_led2; - pchip->bled2 = - backlight_device_register(name, pchip->dev, pchip, - &lm3630_bank_b_ops, &props); - if (IS_ERR(pchip->bled2)) - return PTR_ERR(pchip->bled2); - break; - } - return 0; -} - -static void lm3630_backlight_unregister(struct lm3630_chip_data *pchip) -{ - if (pchip->bled1) - backlight_device_unregister(pchip->bled1); - if (pchip->bled2) - backlight_device_unregister(pchip->bled2); -} - -static const struct regmap_config lm3630_regmap = { - .reg_bits = 8, - .val_bits = 8, - .max_register = REG_MAX, -}; - -static int lm3630_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct lm3630_platform_data *pdata = client->dev.platform_data; - struct lm3630_chip_data *pchip; - int ret; - - if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { - dev_err(&client->dev, "fail : i2c functionality check...\n"); - return -EOPNOTSUPP; - } - - if (pdata == NULL) { - dev_err(&client->dev, "fail : no platform data.\n"); - return -ENODATA; - } - - pchip = devm_kzalloc(&client->dev, sizeof(struct lm3630_chip_data), - GFP_KERNEL); - if (!pchip) - return -ENOMEM; - pchip->pdata = pdata; - pchip->dev = &client->dev; - - pchip->regmap = devm_regmap_init_i2c(client, &lm3630_regmap); - if (IS_ERR(pchip->regmap)) { - ret = PTR_ERR(pchip->regmap); - dev_err(&client->dev, "fail : allocate register map: %d\n", - ret); - return ret; - } - i2c_set_clientdata(client, pchip); - - /* chip initialize */ - ret = lm3630_chip_init(pchip); - if (ret < 0) { - dev_err(&client->dev, "fail : init chip\n"); - goto err_chip_init; - } - - switch (pdata->bank_a_ctrl) { - case BANK_A_CTRL_ALL: - ret = lm3630_backlight_register(pchip, BLED_ALL); - pdata->bank_b_ctrl = BANK_B_CTRL_DISABLE; - break; - case BANK_A_CTRL_LED1: - ret = lm3630_backlight_register(pchip, BLED_1); - break; - case BANK_A_CTRL_LED2: - ret = lm3630_backlight_register(pchip, BLED_2); - pdata->bank_b_ctrl = BANK_B_CTRL_DISABLE; - break; - default: - break; - } - - if (ret < 0) - goto err_bl_reg; - - if (pdata->bank_b_ctrl && pchip->bled2 == NULL) { - ret = lm3630_backlight_register(pchip, BLED_2); - if (ret < 0) - goto err_bl_reg; - } - - /* interrupt enable : irq 0 is not allowed for lm3630 */ - pchip->irq = client->irq; - if (pchip->irq) - lm3630_intr_config(pchip); - - dev_info(&client->dev, "LM3630 backlight register OK.\n"); - return 0; - -err_bl_reg: - dev_err(&client->dev, "fail : backlight register.\n"); - lm3630_backlight_unregister(pchip); -err_chip_init: - return ret; -} - -static int lm3630_remove(struct i2c_client *client) -{ - int ret; - struct lm3630_chip_data *pchip = i2c_get_clientdata(client); - - ret = regmap_write(pchip->regmap, REG_BRT_A, 0); - if (ret < 0) - dev_err(pchip->dev, "i2c failed to access register\n"); - - ret = regmap_write(pchip->regmap, REG_BRT_B, 0); - if (ret < 0) - dev_err(pchip->dev, "i2c failed to access register\n"); - - lm3630_backlight_unregister(pchip); - if (pchip->irq) { - free_irq(pchip->irq, pchip); - flush_workqueue(pchip->irqthread); - destroy_workqueue(pchip->irqthread); - } - return 0; -} - -static const struct i2c_device_id lm3630_id[] = { - {LM3630_NAME, 0}, - {} -}; - -MODULE_DEVICE_TABLE(i2c, lm3630_id); - -static struct i2c_driver lm3630_i2c_driver = { - .driver = { - .name = LM3630_NAME, - }, - .probe = lm3630_probe, - .remove = lm3630_remove, - .id_table = lm3630_id, -}; - -module_i2c_driver(lm3630_i2c_driver); - -MODULE_DESCRIPTION("Texas Instruments Backlight driver for LM3630"); -MODULE_AUTHOR("G.Shark Jeong "); -MODULE_AUTHOR("Daniel Jeong "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/backlight/lm3630a_bl.c b/drivers/video/backlight/lm3630a_bl.c new file mode 100644 index 000000000000..cf40cc8e662b --- /dev/null +++ b/drivers/video/backlight/lm3630a_bl.c @@ -0,0 +1,483 @@ +/* +* Simple driver for Texas Instruments LM3630A Backlight driver chip +* Copyright (C) 2012 Texas Instruments +* +* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REG_CTRL 0x00 +#define REG_BOOST 0x02 +#define REG_CONFIG 0x01 +#define REG_BRT_A 0x03 +#define REG_BRT_B 0x04 +#define REG_I_A 0x05 +#define REG_I_B 0x06 +#define REG_INT_STATUS 0x09 +#define REG_INT_EN 0x0A +#define REG_FAULT 0x0B +#define REG_PWM_OUTLOW 0x12 +#define REG_PWM_OUTHIGH 0x13 +#define REG_MAX 0x1F + +#define INT_DEBOUNCE_MSEC 10 +struct lm3630a_chip { + struct device *dev; + struct delayed_work work; + + int irq; + struct workqueue_struct *irqthread; + struct lm3630a_platform_data *pdata; + struct backlight_device *bleda; + struct backlight_device *bledb; + struct regmap *regmap; + struct pwm_device *pwmd; +}; + +/* i2c access */ +static int lm3630a_read(struct lm3630a_chip *pchip, unsigned int reg) +{ + int rval; + unsigned int reg_val; + + rval = regmap_read(pchip->regmap, reg, ®_val); + if (rval < 0) + return rval; + return reg_val & 0xFF; +} + +static int lm3630a_write(struct lm3630a_chip *pchip, + unsigned int reg, unsigned int data) +{ + return regmap_write(pchip->regmap, reg, data); +} + +static int lm3630a_update(struct lm3630a_chip *pchip, + unsigned int reg, unsigned int mask, + unsigned int data) +{ + return regmap_update_bits(pchip->regmap, reg, mask, data); +} + +/* initialize chip */ +static int lm3630a_chip_init(struct lm3630a_chip *pchip) +{ + int rval; + struct lm3630a_platform_data *pdata = pchip->pdata; + + usleep_range(1000, 2000); + /* set Filter Strength Register */ + rval = lm3630a_write(pchip, 0x50, 0x03); + /* set Cofig. register */ + rval |= lm3630a_update(pchip, REG_CONFIG, 0x07, pdata->pwm_ctrl); + /* set boost control */ + rval |= lm3630a_write(pchip, REG_BOOST, 0x38); + /* set current A */ + rval |= lm3630a_update(pchip, REG_I_A, 0x1F, 0x1F); + /* set current B */ + rval |= lm3630a_write(pchip, REG_I_B, 0x1F); + /* set control */ + rval |= + lm3630a_write(pchip, REG_CTRL, pdata->leda_ctrl | pdata->ledb_ctrl); + usleep_range(1000, 2000); + /* set brightness A and B */ + rval |= lm3630a_write(pchip, REG_BRT_A, pdata->leda_init_brt); + rval |= lm3630a_write(pchip, REG_BRT_B, pdata->ledb_init_brt); + + if (rval < 0) + dev_err(pchip->dev, "i2c failed to access register\n"); + return rval; +} + +/* interrupt handling */ +static void lm3630a_delayed_func(struct work_struct *work) +{ + unsigned int rval; + struct lm3630a_chip *pchip; + + pchip = container_of(work, struct lm3630a_chip, work.work); + + rval = lm3630a_read(pchip, REG_INT_STATUS); + if (rval < 0) { + dev_err(pchip->dev, + "i2c failed to access REG_INT_STATUS Register\n"); + return; + } + + dev_info(pchip->dev, "REG_INT_STATUS Register is 0x%x\n", rval); +} + +static irqreturn_t lm3630a_isr_func(int irq, void *chip) +{ + int rval; + struct lm3630a_chip *pchip = chip; + unsigned long delay = msecs_to_jiffies(INT_DEBOUNCE_MSEC); + + queue_delayed_work(pchip->irqthread, &pchip->work, delay); + + rval = lm3630a_update(pchip, REG_CTRL, 0x80, 0x00); + if (rval < 0) { + dev_err(pchip->dev, "i2c failed to access register\n"); + return IRQ_NONE; + } + return IRQ_HANDLED; +} + +static int lm3630a_intr_config(struct lm3630a_chip *pchip) +{ + int rval; + + rval = lm3630a_write(pchip, REG_INT_EN, 0x87); + if (rval < 0) + return rval; + + INIT_DELAYED_WORK(&pchip->work, lm3630a_delayed_func); + pchip->irqthread = create_singlethread_workqueue("lm3630a-irqthd"); + if (!pchip->irqthread) { + dev_err(pchip->dev, "create irq thread fail\n"); + return -ENOMEM; + } + if (request_threaded_irq + (pchip->irq, NULL, lm3630a_isr_func, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "lm3630a_irq", pchip)) { + dev_err(pchip->dev, "request threaded irq fail\n"); + return -ENOMEM; + } + return rval; +} + +static void lm3630a_pwm_ctrl(struct lm3630a_chip *pchip, int br, int br_max) +{ + unsigned int period = pwm_get_period(pchip->pwmd); + unsigned int duty = br * period / br_max; + + pwm_config(pchip->pwmd, duty, period); + if (duty) + pwm_enable(pchip->pwmd); + else + pwm_disable(pchip->pwmd); +} + +/* update and get brightness */ +static int lm3630a_bank_a_update_status(struct backlight_device *bl) +{ + int ret; + struct lm3630a_chip *pchip = bl_get_data(bl); + enum lm3630a_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl; + + /* pwm control */ + if ((pwm_ctrl & LM3630A_PWM_BANK_A) != 0) { + lm3630a_pwm_ctrl(pchip, bl->props.brightness, + bl->props.max_brightness); + return bl->props.brightness; + } + + /* disable sleep */ + ret = lm3630a_update(pchip, REG_CTRL, 0x80, 0x00); + if (ret < 0) + goto out_i2c_err; + usleep_range(1000, 2000); + /* minimum brightness is 0x04 */ + ret = lm3630a_write(pchip, REG_BRT_A, bl->props.brightness); + if (bl->props.brightness < 0x4) + ret |= lm3630a_update(pchip, REG_CTRL, LM3630A_LEDA_ENABLE, 0); + else + ret |= lm3630a_update(pchip, REG_CTRL, + LM3630A_LEDA_ENABLE, LM3630A_LEDA_ENABLE); + if (ret < 0) + goto out_i2c_err; + return bl->props.brightness; + +out_i2c_err: + dev_err(pchip->dev, "i2c failed to access\n"); + return bl->props.brightness; +} + +static int lm3630a_bank_a_get_brightness(struct backlight_device *bl) +{ + int brightness, rval; + struct lm3630a_chip *pchip = bl_get_data(bl); + enum lm3630a_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl; + + if ((pwm_ctrl & LM3630A_PWM_BANK_A) != 0) { + rval = lm3630a_read(pchip, REG_PWM_OUTHIGH); + if (rval < 0) + goto out_i2c_err; + brightness = (rval & 0x01) << 8; + rval = lm3630a_read(pchip, REG_PWM_OUTLOW); + if (rval < 0) + goto out_i2c_err; + brightness |= rval; + goto out; + } + + /* disable sleep */ + rval = lm3630a_update(pchip, REG_CTRL, 0x80, 0x00); + if (rval < 0) + goto out_i2c_err; + usleep_range(1000, 2000); + rval = lm3630a_read(pchip, REG_BRT_A); + if (rval < 0) + goto out_i2c_err; + brightness = rval; + +out: + bl->props.brightness = brightness; + return bl->props.brightness; +out_i2c_err: + dev_err(pchip->dev, "i2c failed to access register\n"); + return 0; +} + +static const struct backlight_ops lm3630a_bank_a_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = lm3630a_bank_a_update_status, + .get_brightness = lm3630a_bank_a_get_brightness, +}; + +/* update and get brightness */ +static int lm3630a_bank_b_update_status(struct backlight_device *bl) +{ + int ret; + struct lm3630a_chip *pchip = bl_get_data(bl); + enum lm3630a_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl; + + /* pwm control */ + if ((pwm_ctrl & LM3630A_PWM_BANK_B) != 0) { + lm3630a_pwm_ctrl(pchip, bl->props.brightness, + bl->props.max_brightness); + return bl->props.brightness; + } + + /* disable sleep */ + ret = lm3630a_update(pchip, REG_CTRL, 0x80, 0x00); + if (ret < 0) + goto out_i2c_err; + usleep_range(1000, 2000); + /* minimum brightness is 0x04 */ + ret = lm3630a_write(pchip, REG_BRT_B, bl->props.brightness); + if (bl->props.brightness < 0x4) + ret |= lm3630a_update(pchip, REG_CTRL, LM3630A_LEDB_ENABLE, 0); + else + ret |= lm3630a_update(pchip, REG_CTRL, + LM3630A_LEDB_ENABLE, LM3630A_LEDB_ENABLE); + if (ret < 0) + goto out_i2c_err; + return bl->props.brightness; + +out_i2c_err: + dev_err(pchip->dev, "i2c failed to access REG_CTRL\n"); + return bl->props.brightness; +} + +static int lm3630a_bank_b_get_brightness(struct backlight_device *bl) +{ + int brightness, rval; + struct lm3630a_chip *pchip = bl_get_data(bl); + enum lm3630a_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl; + + if ((pwm_ctrl & LM3630A_PWM_BANK_B) != 0) { + rval = lm3630a_read(pchip, REG_PWM_OUTHIGH); + if (rval < 0) + goto out_i2c_err; + brightness = (rval & 0x01) << 8; + rval = lm3630a_read(pchip, REG_PWM_OUTLOW); + if (rval < 0) + goto out_i2c_err; + brightness |= rval; + goto out; + } + + /* disable sleep */ + rval = lm3630a_update(pchip, REG_CTRL, 0x80, 0x00); + if (rval < 0) + goto out_i2c_err; + usleep_range(1000, 2000); + rval = lm3630a_read(pchip, REG_BRT_B); + if (rval < 0) + goto out_i2c_err; + brightness = rval; + +out: + bl->props.brightness = brightness; + return bl->props.brightness; +out_i2c_err: + dev_err(pchip->dev, "i2c failed to access register\n"); + return 0; +} + +static const struct backlight_ops lm3630a_bank_b_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = lm3630a_bank_b_update_status, + .get_brightness = lm3630a_bank_b_get_brightness, +}; + +static int lm3630a_backlight_register(struct lm3630a_chip *pchip) +{ + struct backlight_properties props; + struct lm3630a_platform_data *pdata = pchip->pdata; + + props.type = BACKLIGHT_RAW; + if (pdata->leda_ctrl != LM3630A_LEDA_DISABLE) { + props.brightness = pdata->leda_init_brt; + props.max_brightness = pdata->leda_max_brt; + pchip->bleda = + devm_backlight_device_register(pchip->dev, "lm3630a_leda", + pchip->dev, pchip, + &lm3630a_bank_a_ops, &props); + if (IS_ERR(pchip->bleda)) + return PTR_ERR(pchip->bleda); + } + + if ((pdata->ledb_ctrl != LM3630A_LEDB_DISABLE) && + (pdata->ledb_ctrl != LM3630A_LEDB_ON_A)) { + props.brightness = pdata->ledb_init_brt; + props.max_brightness = pdata->ledb_max_brt; + pchip->bledb = + devm_backlight_device_register(pchip->dev, "lm3630a_ledb", + pchip->dev, pchip, + &lm3630a_bank_b_ops, &props); + if (IS_ERR(pchip->bledb)) + return PTR_ERR(pchip->bledb); + } + return 0; +} + +static const struct regmap_config lm3630a_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = REG_MAX, +}; + +static int lm3630a_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lm3630a_platform_data *pdata = client->dev.platform_data; + struct lm3630a_chip *pchip; + int rval; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "fail : i2c functionality check\n"); + return -EOPNOTSUPP; + } + + pchip = devm_kzalloc(&client->dev, sizeof(struct lm3630a_chip), + GFP_KERNEL); + if (!pchip) + return -ENOMEM; + pchip->dev = &client->dev; + + pchip->regmap = devm_regmap_init_i2c(client, &lm3630a_regmap); + if (IS_ERR(pchip->regmap)) { + rval = PTR_ERR(pchip->regmap); + dev_err(&client->dev, "fail : allocate reg. map: %d\n", rval); + return rval; + } + + i2c_set_clientdata(client, pchip); + if (pdata == NULL) { + pchip->pdata = devm_kzalloc(pchip->dev, + sizeof(struct + lm3630a_platform_data), + GFP_KERNEL); + if (pchip->pdata == NULL) + return -ENOMEM; + /* default values */ + pchip->pdata->leda_ctrl = LM3630A_LEDA_ENABLE; + pchip->pdata->ledb_ctrl = LM3630A_LEDB_ENABLE; + pchip->pdata->leda_max_brt = LM3630A_MAX_BRIGHTNESS; + pchip->pdata->ledb_max_brt = LM3630A_MAX_BRIGHTNESS; + pchip->pdata->leda_init_brt = LM3630A_MAX_BRIGHTNESS; + pchip->pdata->ledb_init_brt = LM3630A_MAX_BRIGHTNESS; + } else { + pchip->pdata = pdata; + } + /* chip initialize */ + rval = lm3630a_chip_init(pchip); + if (rval < 0) { + dev_err(&client->dev, "fail : init chip\n"); + return rval; + } + /* backlight register */ + rval = lm3630a_backlight_register(pchip); + if (rval < 0) { + dev_err(&client->dev, "fail : backlight register.\n"); + return rval; + } + /* pwm */ + if (pdata->pwm_ctrl != LM3630A_PWM_DISABLE) { + pchip->pwmd = devm_pwm_get(pchip->dev, "lm3630a-pwm"); + if (IS_ERR(pchip->pwmd)) { + dev_err(&client->dev, "fail : get pwm device\n"); + return PTR_ERR(pchip->pwmd); + } + } + pchip->pwmd->period = pdata->pwm_period; + + /* interrupt enable : irq 0 is not allowed */ + pchip->irq = client->irq; + if (pchip->irq) { + rval = lm3630a_intr_config(pchip); + if (rval < 0) + return rval; + } + dev_info(&client->dev, "LM3630A backlight register OK.\n"); + return 0; +} + +static int lm3630a_remove(struct i2c_client *client) +{ + int rval; + struct lm3630a_chip *pchip = i2c_get_clientdata(client); + + rval = lm3630a_write(pchip, REG_BRT_A, 0); + if (rval < 0) + dev_err(pchip->dev, "i2c failed to access register\n"); + + rval = lm3630a_write(pchip, REG_BRT_B, 0); + if (rval < 0) + dev_err(pchip->dev, "i2c failed to access register\n"); + + if (pchip->irq) { + free_irq(pchip->irq, pchip); + flush_workqueue(pchip->irqthread); + destroy_workqueue(pchip->irqthread); + } + return 0; +} + +static const struct i2c_device_id lm3630a_id[] = { + {LM3630A_NAME, 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lm3630a_id); + +static struct i2c_driver lm3630a_i2c_driver = { + .driver = { + .name = LM3630A_NAME, + }, + .probe = lm3630a_probe, + .remove = lm3630a_remove, + .id_table = lm3630a_id, +}; + +module_i2c_driver(lm3630a_i2c_driver); + +MODULE_DESCRIPTION("Texas Instruments Backlight driver for LM3630A"); +MODULE_AUTHOR("Daniel Jeong "); +MODULE_AUTHOR("LDD MLP "); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/platform_data/lm3630_bl.h b/include/linux/platform_data/lm3630_bl.h deleted file mode 100644 index 9176dd3f2d63..000000000000 --- a/include/linux/platform_data/lm3630_bl.h +++ /dev/null @@ -1,57 +0,0 @@ -/* -* Simple driver for Texas Instruments LM3630 LED Flash driver chip -* Copyright (C) 2012 Texas Instruments -* -* 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. -* -*/ - -#ifndef __LINUX_LM3630_H -#define __LINUX_LM3630_H - -#define LM3630_NAME "lm3630_bl" - -enum lm3630_pwm_ctrl { - PWM_CTRL_DISABLE = 0, - PWM_CTRL_BANK_A, - PWM_CTRL_BANK_B, - PWM_CTRL_BANK_ALL, -}; - -enum lm3630_pwm_active { - PWM_ACTIVE_HIGH = 0, - PWM_ACTIVE_LOW, -}; - -enum lm3630_bank_a_ctrl { - BANK_A_CTRL_DISABLE = 0x0, - BANK_A_CTRL_LED1 = 0x4, - BANK_A_CTRL_LED2 = 0x1, - BANK_A_CTRL_ALL = 0x5, -}; - -enum lm3630_bank_b_ctrl { - BANK_B_CTRL_DISABLE = 0, - BANK_B_CTRL_LED2, -}; - -struct lm3630_platform_data { - - /* maximum brightness */ - int max_brt_led1; - int max_brt_led2; - - /* initial on brightness */ - int init_brt_led1; - int init_brt_led2; - enum lm3630_pwm_ctrl pwm_ctrl; - enum lm3630_pwm_active pwm_active; - enum lm3630_bank_a_ctrl bank_a_ctrl; - enum lm3630_bank_b_ctrl bank_b_ctrl; - unsigned int pwm_period; - void (*pwm_set_intensity) (int brightness, int max_brightness); -}; - -#endif /* __LINUX_LM3630_H */ diff --git a/include/linux/platform_data/lm3630a_bl.h b/include/linux/platform_data/lm3630a_bl.h new file mode 100644 index 000000000000..7538e38e270b --- /dev/null +++ b/include/linux/platform_data/lm3630a_bl.h @@ -0,0 +1,65 @@ +/* +* Simple driver for Texas Instruments LM3630A LED Flash driver chip +* Copyright (C) 2012 Texas Instruments +* +* 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. +* +*/ + +#ifndef __LINUX_LM3630A_H +#define __LINUX_LM3630A_H + +#define LM3630A_NAME "lm3630a_bl" + +enum lm3630a_pwm_ctrl { + LM3630A_PWM_DISABLE = 0x00, + LM3630A_PWM_BANK_A, + LM3630A_PWM_BANK_B, + LM3630A_PWM_BANK_ALL, + LM3630A_PWM_BANK_A_ACT_LOW = 0x05, + LM3630A_PWM_BANK_B_ACT_LOW, + LM3630A_PWM_BANK_ALL_ACT_LOW, +}; + +enum lm3630a_leda_ctrl { + LM3630A_LEDA_DISABLE = 0x00, + LM3630A_LEDA_ENABLE = 0x04, + LM3630A_LEDA_ENABLE_LINEAR = 0x14, +}; + +enum lm3630a_ledb_ctrl { + LM3630A_LEDB_DISABLE = 0x00, + LM3630A_LEDB_ON_A = 0x01, + LM3630A_LEDB_ENABLE = 0x02, + LM3630A_LEDB_ENABLE_LINEAR = 0x0A, +}; + +#define LM3630A_MAX_BRIGHTNESS 255 +/* + *@leda_init_brt : led a init brightness. 4~255 + *@leda_max_brt : led a max brightness. 4~255 + *@leda_ctrl : led a disable, enable linear, enable exponential + *@ledb_init_brt : led b init brightness. 4~255 + *@ledb_max_brt : led b max brightness. 4~255 + *@ledb_ctrl : led b disable, enable linear, enable exponential + *@pwm_period : pwm period + *@pwm_ctrl : pwm disable, bank a or b, active high or low + */ +struct lm3630a_platform_data { + + /* led a config. */ + int leda_init_brt; + int leda_max_brt; + enum lm3630a_leda_ctrl leda_ctrl; + /* led b config. */ + int ledb_init_brt; + int ledb_max_brt; + enum lm3630a_ledb_ctrl ledb_ctrl; + /* pwm config. */ + unsigned int pwm_period; + enum lm3630a_pwm_ctrl pwm_ctrl; +}; + +#endif /* __LINUX_LM3630A_H */ -- cgit v1.2.3