diff options
Diffstat (limited to 'drivers/gpio/gpio-generic.c')
-rw-r--r-- | drivers/gpio/gpio-generic.c | 473 |
1 files changed, 327 insertions, 146 deletions
diff --git a/drivers/gpio/gpio-generic.c b/drivers/gpio/gpio-generic.c index e4c4f39b62..1902c83960 100644 --- a/drivers/gpio/gpio-generic.c +++ b/drivers/gpio/gpio-generic.c @@ -1,74 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2008 MontaVista Software, Inc. +// SPDX-FileCopyrightText: 2008,2010 Anton Vorontsov <cbouatmailru@gmail.com> + /* - * Generic driver for memory-mapped GPIO controllers. - * - * Based on linux driver by: - * Copyright 2008 MontaVista Software, Inc. - * Copyright 2008,2010 Anton Vorontsov <cbouatmailru@gmail.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. + * Generic driver for memory-mapped GPIO controllers, based on the Linux driver. */ #include <init.h> -#include <malloc.h> #include <linux/err.h> +#include <linux/bug.h> +#include <linux/kernel.h> +#include <linux/compiler.h> +#include <linux/types.h> +#include <errno.h> #include <linux/log2.h> -#include <linux/err.h> +#include <linux/ioport.h> +#include <io.h> #include <linux/basic_mmio_gpio.h> +#include <linux/slab.h> +#include <linux/bitops.h> +#include <driver.h> +#include <of.h> +#include <of_device.h> -static void bgpio_write8(void __iomem *reg, unsigned int data) +static void bgpio_write8(void __iomem *reg, unsigned long data) { writeb(data, reg); } -static unsigned int bgpio_read8(void __iomem *reg) +static unsigned long bgpio_read8(void __iomem *reg) { return readb(reg); } -static void bgpio_write16(void __iomem *reg, unsigned int data) +static void bgpio_write16(void __iomem *reg, unsigned long data) { writew(data, reg); } -static unsigned int bgpio_read16(void __iomem *reg) +static unsigned long bgpio_read16(void __iomem *reg) { return readw(reg); } -static void bgpio_write32(void __iomem *reg, unsigned int data) +static void bgpio_write32(void __iomem *reg, unsigned long data) { writel(data, reg); } -static unsigned int bgpio_read32(void __iomem *reg) +static unsigned long bgpio_read32(void __iomem *reg) { return readl(reg); } -static unsigned int bgpio_pin2mask(struct bgpio_chip *bgc, unsigned int pin) +#if BITS_PER_LONG >= 64 +static void bgpio_write64(void __iomem *reg, unsigned long data) +{ + writeq(data, reg); +} + +static unsigned long bgpio_read64(void __iomem *reg) +{ + return readq(reg); +} +#endif /* BITS_PER_LONG >= 64 */ + +static void bgpio_write16be(void __iomem *reg, unsigned long data) +{ + iowrite16be(data, reg); +} + +static unsigned long bgpio_read16be(void __iomem *reg) { - return 1 << pin; + return ioread16be(reg); } -static unsigned int bgpio_pin2mask_be(struct bgpio_chip *bgc, unsigned int pin) +static void bgpio_write32be(void __iomem *reg, unsigned long data) { - return 1 << (bgc->bits - 1 - pin); + iowrite32be(data, reg); +} + +static unsigned long bgpio_read32be(void __iomem *reg) +{ + return ioread32be(reg); +} + +static unsigned long bgpio_line2mask(struct bgpio_chip *bgc, unsigned int line) +{ + if (bgc->be_bits) + return BIT(bgc->bits - 1 - line); + return BIT(line); +} + +static int bgpio_get_set(struct gpio_chip *gc, unsigned int gpio) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + unsigned long pinmask = bgpio_line2mask(bgc, gpio); + bool dir = !!(bgc->dir & pinmask); + + if (dir) + return !!(bgc->read_reg(bgc->reg_set) & pinmask); + else + return !!(bgc->read_reg(bgc->reg_dat) & pinmask); } static int bgpio_get(struct gpio_chip *gc, unsigned int gpio) { struct bgpio_chip *bgc = to_bgpio_chip(gc); + return !!(bgc->read_reg(bgc->reg_dat) & bgpio_line2mask(bgc, gpio)); +} - return bgc->read_reg(bgc->reg_dat) & bgc->pin2mask(bgc, gpio); +static void bgpio_set_none(struct gpio_chip *gc, unsigned int gpio, int val) +{ } static void bgpio_set(struct gpio_chip *gc, unsigned int gpio, int val) { struct bgpio_chip *bgc = to_bgpio_chip(gc); - unsigned int mask = bgc->pin2mask(bgc, gpio); + unsigned long mask = bgpio_line2mask(bgc, gpio); if (val) bgc->data |= mask; @@ -82,7 +131,7 @@ static void bgpio_set_with_clear(struct gpio_chip *gc, unsigned int gpio, int val) { struct bgpio_chip *bgc = to_bgpio_chip(gc); - unsigned int mask = bgc->pin2mask(bgc, gpio); + unsigned long mask = bgpio_line2mask(bgc, gpio); if (val) bgc->write_reg(bgc->reg_set, mask); @@ -93,7 +142,7 @@ static void bgpio_set_with_clear(struct gpio_chip *gc, unsigned int gpio, static void bgpio_set_set(struct gpio_chip *gc, unsigned int gpio, int val) { struct bgpio_chip *bgc = to_bgpio_chip(gc); - unsigned int mask = bgc->pin2mask(bgc, gpio); + unsigned long mask = bgpio_line2mask(bgc, gpio); if (val) bgc->data |= mask; @@ -108,6 +157,12 @@ static int bgpio_simple_dir_in(struct gpio_chip *gc, unsigned int gpio) return 0; } +static int bgpio_dir_out_err(struct gpio_chip *gc, unsigned int gpio, + int val) +{ + return -EINVAL; +} + static int bgpio_simple_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) { @@ -120,69 +175,115 @@ static int bgpio_dir_in(struct gpio_chip *gc, unsigned int gpio) { struct bgpio_chip *bgc = to_bgpio_chip(gc); - bgc->dir &= ~bgc->pin2mask(bgc, gpio); - bgc->write_reg(bgc->reg_dir, bgc->dir); + bgc->dir &= ~bgpio_line2mask(bgc, gpio); + + if (bgc->reg_dir_in) + bgc->write_reg(bgc->reg_dir_in, ~bgc->dir); + if (bgc->reg_dir_out) + bgc->write_reg(bgc->reg_dir_out, bgc->dir); return 0; } -static int bgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) +static int bgpio_get_dir(struct gpio_chip *gc, unsigned int gpio) { struct bgpio_chip *bgc = to_bgpio_chip(gc); - gc->ops->set(gc, gpio, val); + /* Return 0 if output, 1 if input */ + if (bgc->dir_unreadable) { + if (bgc->dir & bgpio_line2mask(bgc, gpio)) + return GPIOF_DIR_OUT; + return GPIOF_DIR_IN; + } - bgc->dir |= bgc->pin2mask(bgc, gpio); - bgc->write_reg(bgc->reg_dir, bgc->dir); + if (bgc->reg_dir_out) { + if (bgc->read_reg(bgc->reg_dir_out) & bgpio_line2mask(bgc, gpio)) + return GPIOF_DIR_OUT; + return GPIOF_DIR_IN; + } - return 0; + if (bgc->reg_dir_in) + if (!(bgc->read_reg(bgc->reg_dir_in) & bgpio_line2mask(bgc, gpio))) + return GPIOF_DIR_OUT; + + return GPIOF_DIR_IN; } -static int bgpio_dir_in_inv(struct gpio_chip *gc, unsigned int gpio) +static void bgpio_dir_out(struct bgpio_chip *bgc, unsigned int gpio, int val) { - struct bgpio_chip *bgc = to_bgpio_chip(gc); - - bgc->dir |= bgc->pin2mask(bgc, gpio); - bgc->write_reg(bgc->reg_dir, bgc->dir); + bgc->dir |= bgpio_line2mask(bgc, gpio); - return 0; + if (bgc->reg_dir_in) + bgc->write_reg(bgc->reg_dir_in, ~bgc->dir); + if (bgc->reg_dir_out) + bgc->write_reg(bgc->reg_dir_out, bgc->dir); } -static int bgpio_dir_out_inv(struct gpio_chip *gc, unsigned int gpio, int val) +static int bgpio_dir_out_dir_first(struct gpio_chip *gc, unsigned int gpio, + int val) { struct bgpio_chip *bgc = to_bgpio_chip(gc); + bgpio_dir_out(bgc, gpio, val); gc->ops->set(gc, gpio, val); + return 0; +} - bgc->dir &= ~bgc->pin2mask(bgc, gpio); - bgc->write_reg(bgc->reg_dir, bgc->dir); +static int bgpio_dir_out_val_first(struct gpio_chip *gc, unsigned int gpio, + int val) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + gc->ops->set(gc, gpio, val); + bgpio_dir_out(bgc, gpio, val); return 0; } -static int bgpio_setup_accessors(struct device_d *dev, struct bgpio_chip *bgc, - bool be) +static int bgpio_setup_accessors(struct device *dev, + struct bgpio_chip *bgc, + bool byte_be) { + switch (bgc->bits) { case 8: bgc->read_reg = bgpio_read8; bgc->write_reg = bgpio_write8; break; case 16: - bgc->read_reg = bgpio_read16; - bgc->write_reg = bgpio_write16; + if (byte_be) { + bgc->read_reg = bgpio_read16be; + bgc->write_reg = bgpio_write16be; + } else { + bgc->read_reg = bgpio_read16; + bgc->write_reg = bgpio_write16; + } break; case 32: - bgc->read_reg = bgpio_read32; - bgc->write_reg = bgpio_write32; + if (byte_be) { + bgc->read_reg = bgpio_read32be; + bgc->write_reg = bgpio_write32be; + } else { + bgc->read_reg = bgpio_read32; + bgc->write_reg = bgpio_write32; + } + break; +#if BITS_PER_LONG >= 64 + case 64: + if (byte_be) { + dev_err(dev, + "64 bit big endian byte order unsupported\n"); + return -EINVAL; + } else { + bgc->read_reg = bgpio_read64; + bgc->write_reg = bgpio_write64; + } break; +#endif /* BITS_PER_LONG >= 64 */ default: - dev_err(dev, "Unsupported data width %u bits\n", bgc->bits); + dev_err(dev, "unsupported data width %u bits\n", bgc->bits); return -EINVAL; } - bgc->pin2mask = be ? bgpio_pin2mask_be : bgpio_pin2mask; - return 0; } @@ -193,7 +294,7 @@ static int bgpio_setup_accessors(struct device_d *dev, struct bgpio_chip *bgc, * - single input/output register resource (named "dat"). * - set/clear pair (named "set" and "clr"). * - single output register resource and single input resource ("set" and - * dat"). + * dat"). * * For the single output register, this drives a 1 by setting a bit and a zero * by clearing a bit. For the set clr pair, this drives a 1 by setting a bit @@ -211,126 +312,214 @@ static int bgpio_setup_accessors(struct device_d *dev, struct bgpio_chip *bgc, static int bgpio_setup_io(struct bgpio_chip *bgc, void __iomem *dat, void __iomem *set, - void __iomem *clr) + void __iomem *clr, + unsigned long flags) { - if (!dat) - return -EINVAL; + struct gpio_ops *ops = bgc->gc.ops; bgc->reg_dat = dat; + if (!bgc->reg_dat) + return -EINVAL; if (set && clr) { bgc->reg_set = set; bgc->reg_clr = clr; - bgc->gc.ops->set = bgpio_set_with_clear; + ops->set = bgpio_set_with_clear; } else if (set && !clr) { bgc->reg_set = set; - bgc->gc.ops->set = bgpio_set_set; - } else - bgc->gc.ops->set = bgpio_set; + ops->set = bgpio_set_set; + } else if (flags & BGPIOF_NO_OUTPUT) { + ops->set = bgpio_set_none; + } else { + ops->set = bgpio_set; + } - bgc->gc.ops->get = bgpio_get; + if (!(flags & BGPIOF_UNREADABLE_REG_SET) && (flags & BGPIOF_READ_OUTPUT_REG_SET)) + ops->get = bgpio_get_set; + else + ops->get = bgpio_get; return 0; } static int bgpio_setup_direction(struct bgpio_chip *bgc, void __iomem *dirout, - void __iomem *dirin) + void __iomem *dirin, + unsigned long flags) { - if (dirout && dirin) - return -EINVAL; - - if (dirout) { - bgc->reg_dir = dirout; - bgc->gc.ops->direction_output = bgpio_dir_out; - bgc->gc.ops->direction_input = bgpio_dir_in; - } else if (dirin) { - bgc->reg_dir = dirin; - bgc->gc.ops->direction_output = bgpio_dir_out_inv; - bgc->gc.ops->direction_input = bgpio_dir_in_inv; + struct gpio_ops *ops = bgc->gc.ops; + + if (dirout || dirin) { + bgc->reg_dir_out = dirout; + bgc->reg_dir_in = dirin; + if (flags & BGPIOF_NO_SET_ON_INPUT) + ops->direction_output = bgpio_dir_out_dir_first; + else + ops->direction_output = bgpio_dir_out_val_first; + ops->direction_input = bgpio_dir_in; + ops->get_direction = bgpio_get_dir; } else { - bgc->gc.ops->direction_output = bgpio_simple_dir_out; - bgc->gc.ops->direction_input = bgpio_simple_dir_in; + if (flags & BGPIOF_NO_OUTPUT) + ops->direction_output = bgpio_dir_out_err; + else + ops->direction_output = bgpio_simple_dir_out; + ops->direction_input = bgpio_simple_dir_in; } return 0; } -int bgpio_init(struct bgpio_chip *bgc, struct device_d *dev, +static int bgpio_request(struct gpio_chip *chip, unsigned gpio_pin) +{ + if (gpio_pin < chip->ngpio) + return 0; + + return -EINVAL; +} + +/** + * bgpio_init() - Initialize generic GPIO accessor functions + * @bgc: the GPIO chip to set up + * @dev: the parent device of the new GPIO chip (compulsory) + * @sz: the size (width) of the MMIO registers in bytes, typically 1, 2 or 4 + * @dat: MMIO address for the register to READ the value of the GPIO lines, it + * is expected that a 1 in the corresponding bit in this register means the + * line is asserted + * @set: MMIO address for the register to SET the value of the GPIO lines, it is + * expected that we write the line with 1 in this register to drive the GPIO line + * high. + * @clr: MMIO address for the register to CLEAR the value of the GPIO lines, it is + * expected that we write the line with 1 in this register to drive the GPIO line + * low. It is allowed to leave this address as NULL, in that case the SET register + * will be assumed to also clear the GPIO lines, by actively writing the line + * with 0. + * @dirout: MMIO address for the register to set the line as OUTPUT. It is assumed + * that setting a line to 1 in this register will turn that line into an + * output line. Conversely, setting the line to 0 will turn that line into + * an input. + * @dirin: MMIO address for the register to set this line as INPUT. It is assumed + * that setting a line to 1 in this register will turn that line into an + * input line. Conversely, setting the line to 0 will turn that line into + * an output. + * @flags: Different flags that will affect the behaviour of the device, such as + * endianness etc. + */ +int bgpio_init(struct bgpio_chip *bgc, struct device *dev, unsigned int sz, void __iomem *dat, void __iomem *set, void __iomem *clr, void __iomem *dirout, void __iomem *dirin, unsigned long flags) { + struct gpio_ops *ops = &bgc->ops; int ret; - if ((sz > 4) || !is_power_of_2(sz)) + if (!is_power_of_2(sz)) return -EINVAL; bgc->bits = sz * 8; - bgc->gc.ngpio = bgc->bits; + if (bgc->bits > BITS_PER_LONG) + return -EINVAL; + bgc->gc.base = -1; + bgc->gc.ngpio = bgc->bits; bgc->gc.dev = dev; - bgc->gc.ops = &bgc->ops; + bgc->gc.ops = ops; + ops->request = bgpio_request; + bgc->be_bits = !!(flags & BGPIOF_BIG_ENDIAN); - ret = bgpio_setup_io(bgc, dat, set, clr); + ret = bgpio_setup_io(bgc, dat, set, clr, flags); if (ret) return ret; - ret = bgpio_setup_accessors(dev, bgc, flags & BGPIOF_BIG_ENDIAN); + ret = bgpio_setup_accessors(dev, bgc, flags & BGPIOF_BIG_ENDIAN_BYTE_ORDER); if (ret) return ret; - ret = bgpio_setup_direction(bgc, dirout, dirin); + ret = bgpio_setup_direction(bgc, dirout, dirin, flags); if (ret) return ret; bgc->data = bgc->read_reg(bgc->reg_dat); - - if (bgc->gc.ops->set == bgpio_set_set && !(flags & - BGPIOF_UNREADABLE_REG_SET)) + if (ops->set == bgpio_set_set && + !(flags & BGPIOF_UNREADABLE_REG_SET)) bgc->data = bgc->read_reg(bgc->reg_set); - if (bgc->reg_dir && !(flags & BGPIOF_UNREADABLE_REG_DIR)) - bgc->dir = bgc->read_reg(bgc->reg_dir); + if (flags & BGPIOF_UNREADABLE_REG_DIR) + bgc->dir_unreadable = true; + + /* + * Inspect hardware to find initial direction setting. + */ + if ((bgc->reg_dir_out || bgc->reg_dir_in) && + !(flags & BGPIOF_UNREADABLE_REG_DIR)) { + if (bgc->reg_dir_out) + bgc->dir = bgc->read_reg(bgc->reg_dir_out); + else if (bgc->reg_dir_in) + bgc->dir = ~bgc->read_reg(bgc->reg_dir_in); + /* + * If we have two direction registers, synchronise + * input setting to output setting, the library + * can not handle a line being input and output at + * the same time. + */ + if (bgc->reg_dir_out && bgc->reg_dir_in) + bgc->write_reg(bgc->reg_dir_in, ~bgc->dir); + } return ret; } +EXPORT_SYMBOL_GPL(bgpio_init); void bgpio_remove(struct bgpio_chip *bgc) { gpiochip_remove(&bgc->gc); free(bgc); } +EXPORT_SYMBOL_GPL(bgpio_remove); #ifdef CONFIG_GPIO_GENERIC_PLATFORM -static void __iomem *bgpio_map(struct device_d *dev, const char *name, - resource_size_t sane_sz, int *err) +static void __iomem *bgpio_map(struct device *dev, + const char *name, + resource_size_t sane_sz) { struct resource *r; - struct resource *ret; - - *err = 0; + resource_size_t sz; - r = dev_get_resource_by_name(dev, IORESOURCE_MEM, name); + r = dev_request_mem_resource_by_name(dev, name); if (IS_ERR(r)) return NULL; - if (resource_size(r) != sane_sz) { - *err = -EINVAL; - return NULL; - } + sz = resource_size(r); + if (sz != sane_sz) + return IOMEM_ERR_PTR(-EINVAL); - ret = request_iomem_region(dev_name(dev), r->start, r->end); - if (IS_ERR(ret)) { - *err = PTR_ERR(ret); + return IOMEM(r->start); +} + +static const struct of_device_id bgpio_of_match[]; + +static struct bgpio_pdata *bgpio_parse_dt(struct device *dev, + unsigned long *flags) +{ + struct bgpio_pdata *pdata; + + if (!of_match_device(bgpio_of_match, dev)) return NULL; - } - return IOMEM(ret->start); + pdata = xzalloc(sizeof(struct bgpio_pdata)); + + pdata->base = -1; + + if (of_device_is_big_endian(dev->of_node)) + *flags |= BGPIOF_BIG_ENDIAN_BYTE_ORDER; + + if (of_property_read_bool(dev->of_node, "no-output")) + *flags |= BGPIOF_NO_OUTPUT; + + return pdata; } -static int bgpio_dev_probe(struct device_d *dev) +static int bgpio_dev_probe(struct device *dev) { struct resource *r; void __iomem *dat; @@ -342,35 +531,37 @@ static int bgpio_dev_probe(struct device_d *dev) unsigned long flags = 0; int err; struct bgpio_chip *bgc; - struct bgpio_pdata *pdata = dev->platform_data; + struct bgpio_pdata *pdata; + + pdata = bgpio_parse_dt(dev, &flags); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); r = dev_get_resource_by_name(dev, IORESOURCE_MEM, "dat"); - if (IS_ERR(r)) - return PTR_ERR(r); + if (!r) + return -EINVAL; sz = resource_size(r); - dat = bgpio_map(dev, "dat", sz, &err); - if (!dat) - return err ? err : -EINVAL; + dat = bgpio_map(dev, "dat", sz); + if (IS_ERR(dat)) + return PTR_ERR(dat); - set = bgpio_map(dev, "set", sz, &err); - if (err) - return err; + set = bgpio_map(dev, "set", sz); + if (IS_ERR(set)) + return PTR_ERR(set); - clr = bgpio_map(dev, "clr", sz, &err); - if (err) - return err; + clr = bgpio_map(dev, "clr", sz); + if (IS_ERR(clr)) + return PTR_ERR(clr); - dirout = bgpio_map(dev, "dirout", sz, &err); - if (err) - return err; + dirout = bgpio_map(dev, "dirout", sz); + if (IS_ERR(dirout)) + return PTR_ERR(dirout); - dirin = bgpio_map(dev, "dirin", sz, &err); - if (err) - return err; - - dev_get_drvdata(dev, (const void **)&flags); + dirin = bgpio_map(dev, "dirin", sz); + if (IS_ERR(dirin)) + return PTR_ERR(dirin); bgc = xzalloc(sizeof(struct bgpio_chip)); @@ -378,57 +569,47 @@ static int bgpio_dev_probe(struct device_d *dev) if (err) return err; - if (pdata) { - bgc->gc.base = pdata->base; - if (pdata->ngpio > 0) - bgc->gc.ngpio = pdata->ngpio; - } + bgc->gc.base = pdata->base; + bgc->gc.dev = dev; + bgc->gc.ops = &bgc->ops; + if (pdata->ngpio > 0) + bgc->gc.ngpio = pdata->ngpio; dev->priv = bgc; return gpiochip_add(&bgc->gc); } -static void bgpio_dev_remove(struct device_d *dev) +static void bgpio_dev_remove(struct device *dev) { struct bgpio_chip *bgc = dev->priv; bgpio_remove(bgc); } -static struct platform_device_id bgpio_id_table[] = { +static const struct of_device_id bgpio_of_match[] = { { - .name = "basic-mmio-gpio", - .driver_data = 0, + .compatible = "wd,mbl-gpio", }, { - .name = "basic-mmio-gpio-be", - .driver_data = BGPIOF_BIG_ENDIAN, + .compatible = "brcm,bcm6345-gpio" }, - { } -}; - -static struct of_device_id __maybe_unused bgpio_of_match[] = { { - .compatible = "wd,mbl-gpio", + .compatible = "ni,169445-nand-gpio" }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, bgpio_of_match); -static struct driver_d bgpio_driver = { +static struct driver bgpio_driver = { .name = "basic-mmio-gpio", - .id_table = bgpio_id_table, - .of_compatible = DRV_OF_COMPAT(bgpio_of_match), + .of_compatible = bgpio_of_match, .probe = bgpio_dev_probe, .remove = bgpio_dev_remove, }; -static int bgpio_register(void) -{ - return platform_driver_register(&bgpio_driver); -} -coredevice_initcall(bgpio_register); +coredevice_platform_driver(bgpio_driver); #endif |