diff options
| author | Sang-Heon Jeon <ekffu200098@gmail.com> | 2026-08-24 01:18:14 +0900 |
|---|---|---|
| committer | Julia Lawall <Julia.Lawall@inria.fr> | 2026-08-23 22:12:11 +0200 |
| commit | 2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c (patch) | |
| tree | dcc4da1d99f17541c5590ac63c77da4bf372a4ab /drivers/gpio/gpio-74x164.c | |
| download | linux-stable-2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c.tar.gz linux-stable-2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c.zip | |
coccinelle: alloc_cast: drop removed allocatorsgrafted
- pci_alloc_consistent() and pci_zalloc_consistent() were removed by
commit 7968778914e5
("PCI: Remove the deprecated "pci-dma-compat.h" API")
- kmem_alloc() was removed by commit f078d4ea8276
("xfs: convert kmem_alloc() to kmalloc()")
- kmem_zalloc() was removed by commit 10634530f7ba
("xfs: convert kmem_zalloc() to kzalloc()")
- kmem_zone_alloc() and kmem_zone_zalloc() were removed by commit
bae633a4a283 ("xfs: remove xfs_zone_{alloc,zalloc} helpers")
So drop them from the rules.
No functional change.
Signed-off-by: Sang-Heon Jeon <ekffu200098@gmail.com>
Signed-off-by: Julia Lawall <Julia.Lawall@inria.fr>
Diffstat (limited to 'drivers/gpio/gpio-74x164.c')
| -rw-r--r-- | drivers/gpio/gpio-74x164.c | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-74x164.c b/drivers/gpio/gpio-74x164.c new file mode 100644 index 000000000..5ca61cf52 --- /dev/null +++ b/drivers/gpio/gpio-74x164.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 74Hx164 - Generic serial-in/parallel-out 8-bits shift register GPIO driver + * + * Copyright (C) 2010 Gabor Juhos <juhosg@openwrt.org> + * Copyright (C) 2010 Miguel Gaio <miguel.gaio@efixo.com> + */ + +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio/driver.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> + +#define GEN_74X164_NUMBER_GPIOS 8 + +struct gen_74x164_chip { + struct gpio_chip gpio_chip; + struct mutex lock; + struct gpio_desc *gpiod_oe; + u32 registers; + /* + * Since the registers are chained, every byte sent will make + * the previous byte shift to the next register in the + * chain. Thus, the first byte sent will end up in the last + * register at the end of the transfer. So, to have a logical + * numbering, store the bytes in reverse order. + */ + u8 buffer[] __counted_by(registers); +}; + +static int __gen_74x164_write_config(struct gen_74x164_chip *chip) +{ + return spi_write(to_spi_device(chip->gpio_chip.parent), chip->buffer, + chip->registers); +} + +static int gen_74x164_get_value(struct gpio_chip *gc, unsigned offset) +{ + struct gen_74x164_chip *chip = gpiochip_get_data(gc); + u8 bank = chip->registers - 1 - offset / 8; + u8 pin = offset % 8; + + guard(mutex)(&chip->lock); + + return !!(chip->buffer[bank] & BIT(pin)); +} + +static int gen_74x164_set_value(struct gpio_chip *gc, + unsigned int offset, int val) +{ + struct gen_74x164_chip *chip = gpiochip_get_data(gc); + u8 bank = chip->registers - 1 - offset / 8; + u8 pin = offset % 8; + + guard(mutex)(&chip->lock); + + if (val) + chip->buffer[bank] |= BIT(pin); + else + chip->buffer[bank] &= ~BIT(pin); + + return __gen_74x164_write_config(chip); +} + +static int gen_74x164_set_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + struct gen_74x164_chip *chip = gpiochip_get_data(gc); + unsigned long offset; + unsigned long bankmask; + size_t bank; + unsigned long bitmask; + + guard(mutex)(&chip->lock); + + for_each_set_clump8(offset, bankmask, mask, chip->registers * 8) { + bank = chip->registers - 1 - offset / 8; + bitmask = bitmap_get_value8(bits, offset) & bankmask; + + chip->buffer[bank] &= ~bankmask; + chip->buffer[bank] |= bitmask; + } + return __gen_74x164_write_config(chip); +} + +static int gen_74x164_direction_output(struct gpio_chip *gc, + unsigned offset, int val) +{ + gen_74x164_set_value(gc, offset, val); + return 0; +} + +static void gen_74x164_deactivate(void *data) +{ + struct gen_74x164_chip *chip = data; + + gpiod_set_value_cansleep(chip->gpiod_oe, 0); +} + +static int gen_74x164_activate(struct device *dev, struct gen_74x164_chip *chip) +{ + gpiod_set_value_cansleep(chip->gpiod_oe, 1); + return devm_add_action_or_reset(dev, gen_74x164_deactivate, chip); +} + +static int gen_74x164_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct gen_74x164_chip *chip; + u32 nregs, init_state; + int ret; + + /* + * bits_per_word cannot be configured in platform data + */ + spi->bits_per_word = 8; + + ret = spi_setup(spi); + if (ret < 0) + return ret; + + ret = device_property_read_u32(dev, "registers-number", &nregs); + if (ret) + return dev_err_probe(dev, ret, "Missing 'registers-number' property.\n"); + + chip = devm_kzalloc(dev, struct_size(chip, buffer, nregs), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->registers = nregs; + + /* + * Optionally seed the chain with a board-specified pattern so the + * outputs come up in a known state on the first SPI write. The + * property follows the nxp,pcf8575 convention where bit N maps to + * GPIO line N. On this output-only device, bit=0 drives the line + * low and bit=1 drives it high. The bitmask covers up to 32 lines; + * any further outputs come up zeroed by devm_kzalloc(). + */ + if (!device_property_read_u32(dev, "lines-initial-states", &init_state)) { + unsigned int i; + + for (i = 0; i < min(nregs, 4U); i++) + chip->buffer[nregs - 1 - i] = (init_state >> (i * 8)) & 0xff; + } + + chip->gpiod_oe = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(chip->gpiod_oe)) + return PTR_ERR(chip->gpiod_oe); + + chip->gpio_chip.label = spi->modalias; + chip->gpio_chip.direction_output = gen_74x164_direction_output; + chip->gpio_chip.get = gen_74x164_get_value; + chip->gpio_chip.set = gen_74x164_set_value; + chip->gpio_chip.set_multiple = gen_74x164_set_multiple; + chip->gpio_chip.base = -1; + chip->gpio_chip.ngpio = GEN_74X164_NUMBER_GPIOS * chip->registers; + chip->gpio_chip.can_sleep = true; + chip->gpio_chip.parent = dev; + chip->gpio_chip.owner = THIS_MODULE; + + ret = devm_mutex_init(dev, &chip->lock); + if (ret) + return ret; + + ret = __gen_74x164_write_config(chip); + if (ret) + return dev_err_probe(dev, ret, "Config write failed\n"); + + ret = gen_74x164_activate(dev, chip); + if (ret) + return ret; + + return devm_gpiochip_add_data(dev, &chip->gpio_chip, chip); +} + +static const struct spi_device_id gen_74x164_spi_ids[] = { + { .name = "74hc595" }, + { .name = "74lvc594" }, + {}, +}; +MODULE_DEVICE_TABLE(spi, gen_74x164_spi_ids); + +static const struct of_device_id gen_74x164_dt_ids[] = { + { .compatible = "fairchild,74hc595" }, + { .compatible = "nxp,74lvc594" }, + {}, +}; +MODULE_DEVICE_TABLE(of, gen_74x164_dt_ids); + +static struct spi_driver gen_74x164_driver = { + .driver = { + .name = "74x164", + .of_match_table = gen_74x164_dt_ids, + }, + .probe = gen_74x164_probe, + .id_table = gen_74x164_spi_ids, +}; +module_spi_driver(gen_74x164_driver); + +MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); +MODULE_AUTHOR("Miguel Gaio <miguel.gaio@efixo.com>"); +MODULE_DESCRIPTION("GPIO expander driver for 74X164 8-bits shift register"); +MODULE_LICENSE("GPL v2"); |
