| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Take drivers/gpio/gpio-74x164.c as reference. |
| * |
| * 74Hx164 - Generic serial-in/parallel-out 8-bits shift register GPIO driver |
| * |
| * Copyright (C) 2016 Peng Fan <van.freenix@gmail.com> |
| * |
| */ |
| |
| #include <common.h> |
| #include <errno.h> |
| #include <dm.h> |
| #include <fdtdec.h> |
| #include <malloc.h> |
| #include <asm/gpio.h> |
| #include <asm/io.h> |
| #include <dm/device_compat.h> |
| #include <dt-bindings/gpio/gpio.h> |
| #include <spi.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| /* |
| * struct gen_74x164_chip - Data for 74Hx164 |
| * |
| * @oe: OE pin |
| * @nregs: number of registers |
| * @buffer: buffer for chained chips |
| */ |
| #define GEN_74X164_NUMBER_GPIOS 8 |
| |
| struct gen_74x164_priv { |
| struct gpio_desc oe; |
| u32 nregs; |
| /* |
| * Since the nregs 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; |
| }; |
| |
| static int gen_74x164_write_conf(struct udevice *dev) |
| { |
| struct gen_74x164_priv *priv = dev_get_priv(dev); |
| int ret; |
| |
| ret = dm_spi_claim_bus(dev); |
| if (ret) |
| return ret; |
| |
| ret = dm_spi_xfer(dev, priv->nregs * 8, priv->buffer, NULL, |
| SPI_XFER_BEGIN | SPI_XFER_END); |
| |
| dm_spi_release_bus(dev); |
| |
| return ret; |
| } |
| |
| static int gen_74x164_get_value(struct udevice *dev, unsigned offset) |
| { |
| struct gen_74x164_priv *priv = dev_get_priv(dev); |
| uint bank = priv->nregs - 1 - offset / 8; |
| uint pin = offset % 8; |
| |
| return (priv->buffer[bank] >> pin) & 0x1; |
| } |
| |
| static int gen_74x164_set_value(struct udevice *dev, unsigned offset, |
| int value) |
| { |
| struct gen_74x164_priv *priv = dev_get_priv(dev); |
| uint bank = priv->nregs - 1 - offset / 8; |
| uint pin = offset % 8; |
| int ret; |
| |
| if (value) |
| priv->buffer[bank] |= 1 << pin; |
| else |
| priv->buffer[bank] &= ~(1 << pin); |
| |
| ret = gen_74x164_write_conf(dev); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int gen_74x164_direction_input(struct udevice *dev, unsigned offset) |
| { |
| return -ENOSYS; |
| } |
| |
| static int gen_74x164_direction_output(struct udevice *dev, unsigned offset, |
| int value) |
| { |
| return gen_74x164_set_value(dev, offset, value); |
| } |
| |
| static int gen_74x164_get_function(struct udevice *dev, unsigned offset) |
| { |
| return GPIOF_OUTPUT; |
| } |
| |
| static int gen_74x164_xlate(struct udevice *dev, struct gpio_desc *desc, |
| struct ofnode_phandle_args *args) |
| { |
| desc->offset = args->args[0]; |
| desc->flags = args->args[1] & GPIO_ACTIVE_LOW ? GPIOD_ACTIVE_LOW : 0; |
| |
| return 0; |
| } |
| |
| static const struct dm_gpio_ops gen_74x164_ops = { |
| .direction_input = gen_74x164_direction_input, |
| .direction_output = gen_74x164_direction_output, |
| .get_value = gen_74x164_get_value, |
| .set_value = gen_74x164_set_value, |
| .get_function = gen_74x164_get_function, |
| .xlate = gen_74x164_xlate, |
| }; |
| |
| static int gen_74x164_probe(struct udevice *dev) |
| { |
| struct gen_74x164_priv *priv = dev_get_priv(dev); |
| struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); |
| char *str, name[32]; |
| int ret; |
| const void *fdt = gd->fdt_blob; |
| int node = dev_of_offset(dev); |
| |
| snprintf(name, sizeof(name), "%s_", dev->name); |
| str = strdup(name); |
| if (!str) |
| return -ENOMEM; |
| |
| /* |
| * See Linux kernel: |
| * Documentation/devicetree/bindings/gpio/gpio-74x164.txt |
| */ |
| priv->nregs = fdtdec_get_int(fdt, node, "registers-number", 1); |
| priv->buffer = calloc(priv->nregs, sizeof(u8)); |
| if (!priv->buffer) { |
| ret = -ENOMEM; |
| goto free_str; |
| } |
| |
| ret = fdtdec_get_byte_array(fdt, node, "registers-default", |
| priv->buffer, priv->nregs); |
| if (ret) |
| dev_dbg(dev, "No registers-default property\n"); |
| |
| ret = gpio_request_by_name(dev, "oe-gpios", 0, &priv->oe, |
| GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE); |
| if (ret) { |
| dev_dbg(dev, "No oe-pins property\n"); |
| } |
| |
| uc_priv->bank_name = str; |
| uc_priv->gpio_count = priv->nregs * 8; |
| |
| ret = gen_74x164_write_conf(dev); |
| if (ret) |
| goto free_buf; |
| |
| dev_dbg(dev, "%s is ready\n", dev->name); |
| |
| return 0; |
| |
| free_buf: |
| free(priv->buffer); |
| free_str: |
| free(str); |
| return ret; |
| } |
| |
| static const struct udevice_id gen_74x164_ids[] = { |
| { .compatible = "fairchild,74hc595" }, |
| { } |
| }; |
| |
| U_BOOT_DRIVER(74x164) = { |
| .name = "74x164", |
| .id = UCLASS_GPIO, |
| .ops = &gen_74x164_ops, |
| .probe = gen_74x164_probe, |
| .priv_auto = sizeof(struct gen_74x164_priv), |
| .of_match = gen_74x164_ids, |
| }; |