X-Git-Url: https://git.kernelconcepts.de/?p=karo-tx-uboot.git;a=blobdiff_plain;f=drivers%2Fgpio%2Fs5p_gpio.c;h=13d74eb951bc406a6c3aea51f2548201c16ac258;hp=a1bcddcf45583b9d97ef10973783e3d498016f6b;hb=b8809e60cdb5ab09fb28a0903f25b2878483ec04;hpb=1f241263e088a71b8f33f87b03a37c5418d41e2e diff --git a/drivers/gpio/s5p_gpio.c b/drivers/gpio/s5p_gpio.c index a1bcddcf45..13d74eb951 100644 --- a/drivers/gpio/s5p_gpio.c +++ b/drivers/gpio/s5p_gpio.c @@ -2,41 +2,79 @@ * (C) Copyright 2009 Samsung Electronics * Minkyu Kang * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA + * SPDX-License-Identifier: GPL-2.0+ */ #include +#include +#include +#include +#include #include -#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +#define S5P_GPIO_GET_PIN(x) (x % GPIO_PER_BANK) + +#define CON_MASK(val) (0xf << ((val) << 2)) +#define CON_SFR(gpio, cfg) ((cfg) << ((gpio) << 2)) +#define CON_SFR_UNSHIFT(val, gpio) ((val) >> ((gpio) << 2)) + +#define DAT_MASK(gpio) (0x1 << (gpio)) +#define DAT_SET(gpio) (0x1 << (gpio)) + +#define PULL_MASK(gpio) (0x3 << ((gpio) << 1)) +#define PULL_MODE(gpio, pull) ((pull) << ((gpio) << 1)) -#define CON_MASK(x) (0xf << ((x) << 2)) -#define CON_SFR(x, v) ((v) << ((x) << 2)) +#define DRV_MASK(gpio) (0x3 << ((gpio) << 1)) +#define DRV_SET(gpio, mode) ((mode) << ((gpio) << 1)) +#define RATE_MASK(gpio) (0x1 << (gpio + 16)) +#define RATE_SET(gpio) (0x1 << (gpio + 16)) -#define DAT_MASK(x) (0x1 << (x)) -#define DAT_SET(x) (0x1 << (x)) +#define GPIO_NAME_SIZE 20 -#define PULL_MASK(x) (0x3 << ((x) << 1)) -#define PULL_MODE(x, v) ((v) << ((x) << 1)) +/* Platform data for each bank */ +struct exynos_gpio_platdata { + struct s5p_gpio_bank *bank; + const char *bank_name; /* Name of port, e.g. 'gpa0" */ +}; + +/* Information about each bank at run-time */ +struct exynos_bank_info { + char label[GPIO_PER_BANK][GPIO_NAME_SIZE]; + struct s5p_gpio_bank *bank; +}; + +static struct s5p_gpio_bank *s5p_gpio_get_bank(unsigned int gpio) +{ + const struct gpio_info *data; + unsigned int upto; + int i, count; + + data = get_gpio_data(); + count = get_bank_num(); + upto = 0; + + for (i = 0; i < count; i++) { + debug("i=%d, upto=%d\n", i, upto); + if (gpio < data->max_gpio) { + struct s5p_gpio_bank *bank; + bank = (struct s5p_gpio_bank *)data->reg_addr; + bank += (gpio - upto) / GPIO_PER_BANK; + debug("gpio=%d, bank=%p\n", gpio, bank); + return bank; + } + + upto = data->max_gpio; + data++; + } -#define DRV_MASK(x) (0x3 << ((x) << 1)) -#define DRV_SET(x, m) ((m) << ((x) << 1)) -#define RATE_MASK(x) (0x1 << (x + 16)) -#define RATE_SET(x) (0x1 << (x + 16)) + return NULL; +} -void gpio_cfg_pin(struct s5p_gpio_bank *bank, int gpio, int cfg) +static void s5p_gpio_cfg_pin(struct s5p_gpio_bank *bank, int gpio, int cfg) { unsigned int value; @@ -46,12 +84,10 @@ void gpio_cfg_pin(struct s5p_gpio_bank *bank, int gpio, int cfg) writel(value, &bank->con); } -void gpio_direction_output(struct s5p_gpio_bank *bank, int gpio, int en) +static void s5p_gpio_set_value(struct s5p_gpio_bank *bank, int gpio, int en) { unsigned int value; - gpio_cfg_pin(bank, gpio, GPIO_OUTPUT); - value = readl(&bank->dat); value &= ~DAT_MASK(gpio); if (en) @@ -59,31 +95,35 @@ void gpio_direction_output(struct s5p_gpio_bank *bank, int gpio, int en) writel(value, &bank->dat); } -void gpio_direction_input(struct s5p_gpio_bank *bank, int gpio) +#ifdef CONFIG_SPL_BUILD +/* Common GPIO API - SPL does not support driver model yet */ +int gpio_set_value(unsigned gpio, int value) { - gpio_cfg_pin(bank, gpio, GPIO_INPUT); -} + s5p_gpio_set_value(s5p_gpio_get_bank(gpio), + s5p_gpio_get_pin(gpio), value); -void gpio_set_value(struct s5p_gpio_bank *bank, int gpio, int en) + return 0; +} +#else +static int s5p_gpio_get_cfg_pin(struct s5p_gpio_bank *bank, int gpio) { unsigned int value; - value = readl(&bank->dat); - value &= ~DAT_MASK(gpio); - if (en) - value |= DAT_SET(gpio); - writel(value, &bank->dat); + value = readl(&bank->con); + value &= CON_MASK(gpio); + return CON_SFR_UNSHIFT(value, gpio); } -unsigned int gpio_get_value(struct s5p_gpio_bank *bank, int gpio) +static unsigned int s5p_gpio_get_value(struct s5p_gpio_bank *bank, int gpio) { unsigned int value; value = readl(&bank->dat); return !!(value & DAT_MASK(gpio)); } +#endif /* CONFIG_SPL_BUILD */ -void gpio_set_pull(struct s5p_gpio_bank *bank, int gpio, int mode) +static void s5p_gpio_set_pull(struct s5p_gpio_bank *bank, int gpio, int mode) { unsigned int value; @@ -91,8 +131,8 @@ void gpio_set_pull(struct s5p_gpio_bank *bank, int gpio, int mode) value &= ~PULL_MASK(gpio); switch (mode) { - case GPIO_PULL_DOWN: - case GPIO_PULL_UP: + case S5P_GPIO_PULL_DOWN: + case S5P_GPIO_PULL_UP: value |= PULL_MODE(gpio, mode); break; default: @@ -102,7 +142,7 @@ void gpio_set_pull(struct s5p_gpio_bank *bank, int gpio, int mode) writel(value, &bank->pull); } -void gpio_set_drv(struct s5p_gpio_bank *bank, int gpio, int mode) +static void s5p_gpio_set_drv(struct s5p_gpio_bank *bank, int gpio, int mode) { unsigned int value; @@ -110,10 +150,10 @@ void gpio_set_drv(struct s5p_gpio_bank *bank, int gpio, int mode) value &= ~DRV_MASK(gpio); switch (mode) { - case GPIO_DRV_1X: - case GPIO_DRV_2X: - case GPIO_DRV_3X: - case GPIO_DRV_4X: + case S5P_GPIO_DRV_1X: + case S5P_GPIO_DRV_2X: + case S5P_GPIO_DRV_3X: + case S5P_GPIO_DRV_4X: value |= DRV_SET(gpio, mode); break; default: @@ -123,7 +163,7 @@ void gpio_set_drv(struct s5p_gpio_bank *bank, int gpio, int mode) writel(value, &bank->drv); } -void gpio_set_rate(struct s5p_gpio_bank *bank, int gpio, int mode) +static void s5p_gpio_set_rate(struct s5p_gpio_bank *bank, int gpio, int mode) { unsigned int value; @@ -131,8 +171,8 @@ void gpio_set_rate(struct s5p_gpio_bank *bank, int gpio, int mode) value &= ~RATE_MASK(gpio); switch (mode) { - case GPIO_DRV_FAST: - case GPIO_DRV_SLOW: + case S5P_GPIO_DRV_FAST: + case S5P_GPIO_DRV_SLOW: value |= RATE_SET(gpio); break; default: @@ -141,3 +181,291 @@ void gpio_set_rate(struct s5p_gpio_bank *bank, int gpio, int mode) writel(value, &bank->drv); } + +int s5p_gpio_get_pin(unsigned gpio) +{ + return S5P_GPIO_GET_PIN(gpio); +} + +/* Driver model interface */ +#ifndef CONFIG_SPL_BUILD +static int exynos_gpio_get_state(struct udevice *dev, unsigned int offset, + char *buf, int bufsize) +{ + struct gpio_dev_priv *uc_priv = dev->uclass_priv; + struct exynos_bank_info *state = dev_get_priv(dev); + const char *label; + bool is_output; + int size; + int cfg; + + label = state->label[offset]; + cfg = s5p_gpio_get_cfg_pin(state->bank, offset); + is_output = cfg == S5P_GPIO_OUTPUT; + size = snprintf(buf, bufsize, "%s%d: ", + uc_priv->bank_name ? uc_priv->bank_name : "", offset); + buf += size; + bufsize -= size; + if (is_output || cfg == S5P_GPIO_INPUT) { + snprintf(buf, bufsize, "%s: %d [%c]%s%s", + is_output ? "out" : " in", + s5p_gpio_get_value(state->bank, offset), + *label ? 'x' : ' ', + *label ? " " : "", + label); + } else { + snprintf(buf, bufsize, "sfpio"); + } + + return 0; +} + +static int check_reserved(struct udevice *dev, unsigned offset, + const char *func) +{ + struct exynos_bank_info *state = dev_get_priv(dev); + struct gpio_dev_priv *uc_priv = dev->uclass_priv; + + if (!*state->label[offset]) { + printf("exynos_gpio: %s: error: gpio %s%d not reserved\n", + func, uc_priv->bank_name, offset); + return -EPERM; + } + + return 0; +} + +/* set GPIO pin 'gpio' as an input */ +static int exynos_gpio_direction_input(struct udevice *dev, unsigned offset) +{ + struct exynos_bank_info *state = dev_get_priv(dev); + int ret; + + ret = check_reserved(dev, offset, __func__); + if (ret) + return ret; + + /* Configure GPIO direction as input. */ + s5p_gpio_cfg_pin(state->bank, offset, S5P_GPIO_INPUT); + + return 0; +} + +/* set GPIO pin 'gpio' as an output, with polarity 'value' */ +static int exynos_gpio_direction_output(struct udevice *dev, unsigned offset, + int value) +{ + struct exynos_bank_info *state = dev_get_priv(dev); + int ret; + + ret = check_reserved(dev, offset, __func__); + if (ret) + return ret; + + /* Configure GPIO output value. */ + s5p_gpio_set_value(state->bank, offset, value); + + /* Configure GPIO direction as output. */ + s5p_gpio_cfg_pin(state->bank, offset, S5P_GPIO_OUTPUT); + + return 0; +} + +/* read GPIO IN value of pin 'gpio' */ +static int exynos_gpio_get_value(struct udevice *dev, unsigned offset) +{ + struct exynos_bank_info *state = dev_get_priv(dev); + int ret; + + ret = check_reserved(dev, offset, __func__); + if (ret) + return ret; + + return s5p_gpio_get_value(state->bank, offset); +} + +/* write GPIO OUT value to pin 'gpio' */ +static int exynos_gpio_set_value(struct udevice *dev, unsigned offset, + int value) +{ + struct exynos_bank_info *state = dev_get_priv(dev); + int ret; + + ret = check_reserved(dev, offset, __func__); + if (ret) + return ret; + + s5p_gpio_set_value(state->bank, offset, value); + + return 0; +} + +static int exynos_gpio_request(struct udevice *dev, unsigned offset, + const char *label) +{ + struct exynos_bank_info *state = dev_get_priv(dev); + + if (*state->label[offset]) + return -EBUSY; + + strncpy(state->label[offset], label, GPIO_NAME_SIZE); + state->label[offset][GPIO_NAME_SIZE - 1] = '\0'; + + return 0; +} + +static int exynos_gpio_free(struct udevice *dev, unsigned offset) +{ + struct exynos_bank_info *state = dev_get_priv(dev); + int ret; + + ret = check_reserved(dev, offset, __func__); + if (ret) + return ret; + state->label[offset][0] = '\0'; + + return 0; +} +#endif /* nCONFIG_SPL_BUILD */ + +/* + * There is no common GPIO API for pull, drv, pin, rate (yet). These + * functions are kept here to preserve function ordering for review. + */ +void gpio_set_pull(int gpio, int mode) +{ + s5p_gpio_set_pull(s5p_gpio_get_bank(gpio), + s5p_gpio_get_pin(gpio), mode); +} + +void gpio_set_drv(int gpio, int mode) +{ + s5p_gpio_set_drv(s5p_gpio_get_bank(gpio), + s5p_gpio_get_pin(gpio), mode); +} + +void gpio_cfg_pin(int gpio, int cfg) +{ + s5p_gpio_cfg_pin(s5p_gpio_get_bank(gpio), + s5p_gpio_get_pin(gpio), cfg); +} + +void gpio_set_rate(int gpio, int mode) +{ + s5p_gpio_set_rate(s5p_gpio_get_bank(gpio), + s5p_gpio_get_pin(gpio), mode); +} + +#ifndef CONFIG_SPL_BUILD +static int exynos_gpio_get_function(struct udevice *dev, unsigned offset) +{ + struct exynos_bank_info *state = dev_get_priv(dev); + int cfg; + + if (!*state->label[offset]) + return GPIOF_UNUSED; + cfg = s5p_gpio_get_cfg_pin(state->bank, offset); + if (cfg == S5P_GPIO_OUTPUT) + return GPIOF_OUTPUT; + else if (cfg == S5P_GPIO_INPUT) + return GPIOF_INPUT; + else + return GPIOF_FUNC; +} + +static const struct dm_gpio_ops gpio_exynos_ops = { + .request = exynos_gpio_request, + .free = exynos_gpio_free, + .direction_input = exynos_gpio_direction_input, + .direction_output = exynos_gpio_direction_output, + .get_value = exynos_gpio_get_value, + .set_value = exynos_gpio_set_value, + .get_function = exynos_gpio_get_function, + .get_state = exynos_gpio_get_state, +}; + +static int gpio_exynos_probe(struct udevice *dev) +{ + struct gpio_dev_priv *uc_priv = dev->uclass_priv; + struct exynos_bank_info *priv = dev->priv; + struct exynos_gpio_platdata *plat = dev->platdata; + + /* Only child devices have ports */ + if (!plat) + return 0; + + priv->bank = plat->bank; + + uc_priv->gpio_count = GPIO_PER_BANK; + uc_priv->bank_name = plat->bank_name; + + return 0; +} + +/** + * We have a top-level GPIO device with no actual GPIOs. It has a child + * device for each Exynos GPIO bank. + */ +static int gpio_exynos_bind(struct udevice *parent) +{ + struct exynos_gpio_platdata *plat = parent->platdata; + struct s5p_gpio_bank *bank, *base; + const void *blob = gd->fdt_blob; + int node; + + /* If this is a child device, there is nothing to do here */ + if (plat) + return 0; + + base = (struct s5p_gpio_bank *)fdtdec_get_addr(gd->fdt_blob, + parent->of_offset, "reg"); + for (node = fdt_first_subnode(blob, parent->of_offset), bank = base; + node > 0; + node = fdt_next_subnode(blob, node), bank++) { + struct exynos_gpio_platdata *plat; + struct udevice *dev; + fdt_addr_t reg; + int ret; + + if (!fdtdec_get_bool(blob, node, "gpio-controller")) + continue; + plat = calloc(1, sizeof(*plat)); + if (!plat) + return -ENOMEM; + reg = fdtdec_get_addr(blob, node, "reg"); + if (reg != FDT_ADDR_T_NONE) + bank = (struct s5p_gpio_bank *)((ulong)base + reg); + plat->bank = bank; + plat->bank_name = fdt_get_name(blob, node, NULL); + debug("dev at %p: %s\n", bank, plat->bank_name); + + ret = device_bind(parent, parent->driver, + plat->bank_name, plat, -1, &dev); + if (ret) + return ret; + dev->of_offset = parent->of_offset; + } + + return 0; +} + +static const struct udevice_id exynos_gpio_ids[] = { + { .compatible = "samsung,s5pc100-pinctrl" }, + { .compatible = "samsung,s5pc110-pinctrl" }, + { .compatible = "samsung,exynos4210-pinctrl" }, + { .compatible = "samsung,exynos4x12-pinctrl" }, + { .compatible = "samsung,exynos5250-pinctrl" }, + { .compatible = "samsung,exynos5420-pinctrl" }, + { } +}; + +U_BOOT_DRIVER(gpio_exynos) = { + .name = "gpio_exynos", + .id = UCLASS_GPIO, + .of_match = exynos_gpio_ids, + .bind = gpio_exynos_bind, + .probe = gpio_exynos_probe, + .priv_auto_alloc_size = sizeof(struct exynos_bank_info), + .ops = &gpio_exynos_ops, +}; +#endif