From 606f7047603422746d112e41937649d44f311af4 Mon Sep 17 00:00:00 2001 From: "Albert ARIBAUD \\(3ADEV\\)" Date: Tue, 31 Mar 2015 11:40:46 +0200 Subject: [PATCH] lpc32xx: add GPIO support This driver only supports Driver Model, not legacy model. Signed-off-by: Albert ARIBAUD (3ADEV) --- arch/arm/cpu/arm926ejs/lpc32xx/devices.c | 5 + arch/arm/include/asm/arch-lpc32xx/gpio.h | 43 ++++ drivers/gpio/Kconfig | 7 + drivers/gpio/Makefile | 1 + drivers/gpio/lpc32xx_gpio.c | 293 +++++++++++++++++++++++ 5 files changed, 349 insertions(+) create mode 100644 arch/arm/include/asm/arch-lpc32xx/gpio.h create mode 100644 drivers/gpio/lpc32xx_gpio.c diff --git a/arch/arm/cpu/arm926ejs/lpc32xx/devices.c b/arch/arm/cpu/arm926ejs/lpc32xx/devices.c index 81b53eaa6d..a407098eda 100644 --- a/arch/arm/cpu/arm926ejs/lpc32xx/devices.c +++ b/arch/arm/cpu/arm926ejs/lpc32xx/devices.c @@ -9,6 +9,7 @@ #include #include #include +#include static struct clk_pm_regs *clk = (struct clk_pm_regs *)CLK_PM_BASE; static struct uart_ctrl_regs *ctrl = (struct uart_ctrl_regs *)UART_CTRL_BASE; @@ -61,3 +62,7 @@ void lpc32xx_i2c_init(unsigned int devnum) ctrl |= CLK_I2C2_ENABLE; writel(ctrl, &clk->i2cclk_ctrl); } + +U_BOOT_DEVICE(lpc32xx_gpios) = { + .name = "gpio_lpc32xx" +}; diff --git a/arch/arm/include/asm/arch-lpc32xx/gpio.h b/arch/arm/include/asm/arch-lpc32xx/gpio.h new file mode 100644 index 0000000000..3bd94e31a5 --- /dev/null +++ b/arch/arm/include/asm/arch-lpc32xx/gpio.h @@ -0,0 +1,43 @@ +/* + * LPC32xx GPIO interface + * + * (C) Copyright 2014 DENX Software Engineering GmbH + * Written-by: Albert ARIBAUD + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +/** + * GPIO Register map for LPC32xx + */ + +struct gpio_regs { + u32 p3_inp_state; + u32 p3_outp_set; + u32 p3_outp_clr; + u32 p3_outp_state; + /* Watch out! the following are shared between p2 and p3 */ + u32 p2_p3_dir_set; + u32 p2_p3_dir_clr; + u32 p2_p3_dir_state; + /* Now back to 'one register for one port' */ + u32 p2_inp_state; + u32 p2_outp_set; + u32 p2_outp_clr; + u32 reserved1[6]; + u32 p0_inp_state; + u32 p0_outp_set; + u32 p0_outp_clr; + u32 p0_outp_state; + u32 p0_dir_set; + u32 p0_dir_clr; + u32 p0_dir_state; + u32 reserved2; + u32 p1_inp_state; + u32 p1_outp_set; + u32 p1_outp_clr; + u32 p1_outp_state; + u32 p1_dir_set; + u32 p1_dir_clr; + u32 p1_dir_state; +}; diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index b609e73bba..7b5178a23a 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -7,3 +7,10 @@ config DM_GPIO the GPIO uclass. Drivers provide methods to query the particular GPIOs that they provide. The uclass interface is defined in include/asm-generic/gpio.h. + +config LPC32XX_GPIO + bool "LPC32XX GPIO driver" + depends on DM + default n + help + Support for the LPC32XX GPIO driver. diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index fe9a3b2396..85f71c5d4a 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -41,3 +41,4 @@ obj-$(CONFIG_ADI_GPIO2) += adi_gpio2.o obj-$(CONFIG_TCA642X) += tca642x.o oby-$(CONFIG_SX151X) += sx151x.o obj-$(CONFIG_SUNXI_GPIO) += sunxi_gpio.o +obj-$(CONFIG_LPC32XX_GPIO) += lpc32xx_gpio.o diff --git a/drivers/gpio/lpc32xx_gpio.c b/drivers/gpio/lpc32xx_gpio.c new file mode 100644 index 0000000000..96b312592b --- /dev/null +++ b/drivers/gpio/lpc32xx_gpio.c @@ -0,0 +1,293 @@ +/* + * LPC32xxGPIO driver + * + * (C) Copyright 2014 DENX Software Engineering GmbH + * Written-by: Albert ARIBAUD + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include + +/** + * LPC32xx GPIOs work in banks but are non-homogeneous: + * - each bank holds a different number of GPIOs + * - some GPIOs are input/ouput, some input only, some output only; + * - some GPIOs have different meanings as an input and as an output; + * - some GPIOs are controlled on a given port and bit index, but + * read on another one. +* + * In order to keep this code simple, GPIOS are considered here as + * homogeneous and linear, from 0 to 127. + * + * ** WARNING #1 ** + * + * Client code is responsible for properly using valid GPIO numbers, + * including cases where a single physical GPIO has differing numbers + * for setting its direction, reading it and/or writing to it. + * + * ** WARNING #2 ** + * + * Please read NOTE in description of lpc32xx_gpio_get_function(). + */ + +#define LPC32XX_GPIOS 128 + +struct lpc32xx_gpio_platdata { + struct gpio_regs *regs; + /* GPIO FUNCTION: SEE WARNING #2 */ + signed char function[LPC32XX_GPIOS]; +}; + +/** + * We have 4 GPIO ports of 32 bits each + */ + +#define MAX_GPIO 128 + +#define GPIO_TO_PORT(gpio) ((gpio / 32) & 3) +#define GPIO_TO_RANK(gpio) (gpio % 32) +#define GPIO_TO_MASK(gpio) (1 << (gpio % 32)) + +/** + * Configure a GPIO number 'offset' as input + */ + +static int lpc32xx_gpio_direction_input(struct udevice *dev, unsigned offset) +{ + int port, mask; + struct lpc32xx_gpio_platdata *gpio_platdata = dev_get_platdata(dev); + struct gpio_regs *regs = gpio_platdata->regs; + + port = GPIO_TO_PORT(offset); + mask = GPIO_TO_MASK(offset); + + switch (port) { + case 0: + writel(mask, ®s->p0_dir_clr); + break; + case 1: + writel(mask, ®s->p1_dir_clr); + break; + case 2: + /* ports 2 and 3 share a common direction */ + case 3: + writel(mask, ®s->p2_p3_dir_clr); + break; + default: + return -1; + } + + /* GPIO FUNCTION: SEE WARNING #2 */ + gpio_platdata->function[offset] = GPIOF_INPUT; + + return 0; +} + +/** + * Get the value of a GPIO + */ + +static int lpc32xx_gpio_get_value(struct udevice *dev, unsigned offset) +{ + int port, rank, mask, value; + struct lpc32xx_gpio_platdata *gpio_platdata = dev_get_platdata(dev); + struct gpio_regs *regs = gpio_platdata->regs; + + port = GPIO_TO_PORT(offset); + + switch (port) { + case 0: + value = readl(®s->p0_inp_state); + break; + case 1: + value = readl(®s->p1_inp_state); + break; + case 2: + value = readl(®s->p2_inp_state); + break; + case 3: + value = readl(®s->p3_inp_state); + break; + default: + return -1; + } + + rank = GPIO_TO_RANK(offset); + mask = GPIO_TO_MASK(offset); + + return (value & mask) >> rank; +} + +/** + * Set a GPIO + */ + +static int gpio_set(struct udevice *dev, unsigned gpio) +{ + int port, mask; + struct lpc32xx_gpio_platdata *gpio_platdata = dev_get_platdata(dev); + struct gpio_regs *regs = gpio_platdata->regs; + + port = GPIO_TO_PORT(gpio); + mask = GPIO_TO_MASK(gpio); + + switch (port) { + case 0: + writel(mask, ®s->p0_outp_set); + break; + case 1: + writel(mask, ®s->p1_outp_set); + break; + case 2: + writel(mask, ®s->p2_outp_set); + break; + case 3: + writel(mask, ®s->p3_outp_set); + break; + default: + return -1; + } + return 0; +} + +/** + * Clear a GPIO + */ + +static int gpio_clr(struct udevice *dev, unsigned gpio) +{ + int port, mask; + struct lpc32xx_gpio_platdata *gpio_platdata = dev_get_platdata(dev); + struct gpio_regs *regs = gpio_platdata->regs; + + port = GPIO_TO_PORT(gpio); + mask = GPIO_TO_MASK(gpio); + + switch (port) { + case 0: + writel(mask, ®s->p0_outp_clr); + break; + case 1: + writel(mask, ®s->p1_outp_clr); + break; + case 2: + writel(mask, ®s->p2_outp_clr); + break; + case 3: + writel(mask, ®s->p3_outp_clr); + break; + default: + return -1; + } + return 0; +} + +/** + * Set the value of a GPIO + */ + +static int lpc32xx_gpio_set_value(struct udevice *dev, unsigned offset, + int value) +{ + if (value) + return gpio_set(dev, offset); + else + return gpio_clr(dev, offset); +} + +/** + * Configure a GPIO number 'offset' as output with given initial value. + */ + +static int lpc32xx_gpio_direction_output(struct udevice *dev, unsigned offset, + int value) +{ + int port, mask; + struct lpc32xx_gpio_platdata *gpio_platdata = dev_get_platdata(dev); + struct gpio_regs *regs = gpio_platdata->regs; + + port = GPIO_TO_PORT(offset); + mask = GPIO_TO_MASK(offset); + + switch (port) { + case 0: + writel(mask, ®s->p0_dir_set); + break; + case 1: + writel(mask, ®s->p1_dir_set); + break; + case 2: + /* ports 2 and 3 share a common direction */ + case 3: + writel(mask, ®s->p2_p3_dir_set); + break; + default: + return -1; + } + + /* GPIO FUNCTION: SEE WARNING #2 */ + gpio_platdata->function[offset] = GPIOF_OUTPUT; + + return lpc32xx_gpio_set_value(dev, offset, value); +} + +/** + * GPIO functions are supposed to be computed from their current + * configuration, but that's way too complicated in LPC32XX. A simpler + * approach is used, where the GPIO functions are cached in an array. + * When the GPIO is in use, its function is either "input" or "output" + * depending on its direction, otherwise its function is "unknown". + * + * ** NOTE ** + * + * THIS APPROACH WAS CHOSEN DU TO THE COMPLEX NATURE OF THE LPC32XX + * GPIOS; DO NOT TAKE THIS AS AN EXAMPLE FOR NEW CODE. + */ + +static int lpc32xx_gpio_get_function(struct udevice *dev, unsigned offset) +{ + struct lpc32xx_gpio_platdata *gpio_platdata = dev_get_platdata(dev); + return gpio_platdata->function[offset]; +} + +static const struct dm_gpio_ops gpio_lpc32xx_ops = { + .direction_input = lpc32xx_gpio_direction_input, + .direction_output = lpc32xx_gpio_direction_output, + .get_value = lpc32xx_gpio_get_value, + .set_value = lpc32xx_gpio_set_value, + .get_function = lpc32xx_gpio_get_function, +}; + +static int lpc32xx_gpio_probe(struct udevice *dev) +{ + struct lpc32xx_gpio_platdata *gpio_platdata = dev_get_platdata(dev); + struct gpio_dev_priv *uc_priv = dev->uclass_priv; + + if (dev->of_offset == -1) { + /* Tell the uclass how many GPIOs we have */ + uc_priv->gpio_count = LPC32XX_GPIOS; + } + + /* set base address for GPIO registers */ + gpio_platdata->regs = (struct gpio_regs *)GPIO_BASE; + + /* all GPIO functions are unknown until requested */ + /* GPIO FUNCTION: SEE WARNING #2 */ + memset(gpio_platdata->function, GPIOF_UNKNOWN, + sizeof(gpio_platdata->function)); + + return 0; +} + +U_BOOT_DRIVER(gpio_lpc32xx) = { + .name = "gpio_lpc32xx", + .id = UCLASS_GPIO, + .ops = &gpio_lpc32xx_ops, + .probe = lpc32xx_gpio_probe, + .priv_auto_alloc_size = sizeof(struct lpc32xx_gpio_platdata), +};