[PATCH] gpio: sn54hc595: new driver for GPIO shift registers chipsets

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




SN54HC595 and SN74HC595 are devices based on shift registers controlled
with 5 input signals (serial-in) and providing 8 outputs (parallel-out).

They are present on some Broadcom home router boards where manufacturer
needed few extra GPIOs.

This driver simply uses specified GPIOs to control shift registers and
registers another GPIO chip. So you can call it a GPIO extender.

Due to the hardware design only output direction is supported. Reading
values is handled using tiny internal cache.

Signed-off-by: Rafał Miłecki <zajec5@xxxxxxxxx>
---
 .../devicetree/bindings/gpio/gpio-sn54hc595.txt    |  35 ++++
 drivers/gpio/Kconfig                               |  11 ++
 drivers/gpio/Makefile                              |   1 +
 drivers/gpio/gpio-sn54hc595.c                      | 219 +++++++++++++++++++++
 4 files changed, 266 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gpio/gpio-sn54hc595.txt
 create mode 100644 drivers/gpio/gpio-sn54hc595.c

diff --git a/Documentation/devicetree/bindings/gpio/gpio-sn54hc595.txt b/Documentation/devicetree/bindings/gpio/gpio-sn54hc595.txt
new file mode 100644
index 0000000..034e490
--- /dev/null
+++ b/Documentation/devicetree/bindings/gpio/gpio-sn54hc595.txt
@@ -0,0 +1,35 @@
+GPIO controller based on SN54HC595 / SN74HC595 shift registers
+==============================================================
+
+Required properties:
+
+- compatible : ti,sn54hc595
+
+- ser-gpios : GPIO connected to the serial (SER) input
+
+- srclk-gpios : GPIO connected to the shift register clock (SRCLK) input
+
+- srclr-gpios : GPIO connected to the overriding clear (SRCLR) input
+
+- rclk-gpios : GPIO connected to the register clock (RCLK) input
+
+- oe-gpios : GPIO connected to the output-enable (OE) input
+
+- gpio-controller: Marks the device node as a GPIO controller.
+
+- #gpio-cells : Should be two. Pin number and the optional parameters.
+
+Example:
+
+	sn54hc595 {
+		compatible = "ti,sn54hc595";
+
+		ser-gpios = <&gpio0 3 0>;
+		srclk-gpios = <&gpio0 4 0>;
+		srclr-gpios = <&gpio0 5 0>;
+		rclk-gpios = <&gpio0 6 0>;
+		oe-gpios = <&gpio0 7 0>;
+
+		gpio-controller;
+		#gpio-cells = <2>;
+	};
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 0959ca9..3250870 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -457,6 +457,17 @@ config GPIO_TB10X
 	select GENERIC_IRQ_CHIP
 	select OF_GPIO
 
+comment "GPIO to GPIO expanders:"
+
+config GPIO_SN54HC595
+	tristate "SN54HC595 / SN74HC595 SIPO shift register"
+	depends on OF
+	help
+	  The 'HC595 devices contain 8 shift registers and are controlled using
+	  5 input signals.
+	  This driver controls inputs with 5 GPIOs and registers GPIO chip with
+	  8 outputs.
+
 comment "I2C GPIO expanders:"
 
 config GPIO_ARIZONA
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index e5d346c..37be84d 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -74,6 +74,7 @@ obj-$(CONFIG_GPIO_SAMSUNG)	+= gpio-samsung.o
 obj-$(CONFIG_ARCH_SA1100)	+= gpio-sa1100.o
 obj-$(CONFIG_GPIO_SCH)		+= gpio-sch.o
 obj-$(CONFIG_GPIO_SCH311X)	+= gpio-sch311x.o
+obj-$(CONFIG_GPIO_SN54HC595)	+= gpio-sn54hc595.o
 obj-$(CONFIG_GPIO_SODAVILLE)	+= gpio-sodaville.o
 obj-$(CONFIG_GPIO_SPEAR_SPICS)	+= gpio-spear-spics.o
 obj-$(CONFIG_GPIO_STA2X11)	+= gpio-sta2x11.o
diff --git a/drivers/gpio/gpio-sn54hc595.c b/drivers/gpio/gpio-sn54hc595.c
new file mode 100644
index 0000000..9ff4c3b
--- /dev/null
+++ b/drivers/gpio/gpio-sn54hc595.c
@@ -0,0 +1,219 @@
+/*
+ * Driver for SN54HC595 / SN74HC595 devices.
+ *
+ * Copyright (C) 2014 Rafał Miłecki <zajec5@xxxxxxxxx>
+ *
+ * Licensed under the GNU/GPL. See COPYING for details.
+ */
+
+#include <linux/gpio.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define SN54HC595_NUM_GPIOS		8
+
+struct sn54hc595 {
+	struct gpio_chip	gpio_chip;
+
+	int			values[SN54HC595_NUM_GPIOS];
+
+	unsigned		ser;
+	unsigned		srclk;
+	unsigned		srclr;
+	unsigned		rclk;
+	unsigned		oe;
+};
+
+static inline struct sn54hc595 *gpio_chip_to_sn54hc595(struct gpio_chip *chip)
+{
+	return container_of(chip, struct sn54hc595, gpio_chip);
+}
+
+static void sn54hc595_update_values(struct sn54hc595 *sn54hc595)
+{
+	int i;
+
+	for (i = 0; i < SN54HC595_NUM_GPIOS; i++) {
+		gpio_set_value(sn54hc595->ser, sn54hc595->values[i]);
+
+		gpio_set_value(sn54hc595->srclk, 1);
+		gpio_set_value(sn54hc595->srclk, 0);
+	}
+
+	gpio_set_value(sn54hc595->rclk, 1);
+	gpio_set_value(sn54hc595->rclk, 0);
+}
+
+static int sn54hc595_direction_input(struct gpio_chip *chip, unsigned gpio)
+{
+	return -ENOTSUPP;
+}
+
+static int sn54hc595_direction_output(struct gpio_chip *chip, unsigned gpio,
+				      int value)
+{
+	struct sn54hc595 *sn54hc595 = gpio_chip_to_sn54hc595(chip);
+
+	sn54hc595->values[gpio] = value;
+	sn54hc595_update_values(sn54hc595);
+
+	return 0;
+}
+
+static int sn54hc595_get_value(struct gpio_chip *chip, unsigned gpio)
+{
+	struct sn54hc595 *sn54hc595 = gpio_chip_to_sn54hc595(chip);
+
+	return sn54hc595->values[gpio];
+}
+
+static void sn54hc595_set_value(struct gpio_chip *chip, unsigned gpio,
+				int value)
+{
+	struct sn54hc595 *sn54hc595 = gpio_chip_to_sn54hc595(chip);
+
+	sn54hc595->values[gpio] = value;
+	sn54hc595_update_values(sn54hc595);
+}
+
+static int sn54hc595_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct sn54hc595 *sn54hc595;
+	int err;
+
+	sn54hc595 = devm_kzalloc(dev, sizeof(*sn54hc595), GFP_KERNEL);
+	if (!sn54hc595)
+		return -ENOMEM;
+
+	sn54hc595->ser = of_get_named_gpio_flags(np, "ser-gpios", 0, NULL);
+	if (sn54hc595->ser < 0) {
+		dev_err(dev, "Couldn't read \"src\" property from the DT\n");
+		return -EINVAL;
+	}
+
+	sn54hc595->srclk = of_get_named_gpio_flags(np, "srclk-gpios", 0, NULL);
+	if (sn54hc595->srclk < 0) {
+		dev_err(dev, "Couldn't read \"srclk\" property from the DT\n");
+		return -EINVAL;
+	}
+
+	sn54hc595->srclr = of_get_named_gpio_flags(np, "srclr-gpios", 0, NULL);
+	if (sn54hc595->srclr < 0) {
+		dev_err(dev, "Couldn't read \"srclr\" property from the DT\n");
+		return -EINVAL;
+	}
+
+	sn54hc595->rclk = of_get_named_gpio_flags(np, "rclk-gpios", 0, NULL);
+	if (sn54hc595->rclk < 0) {
+		dev_err(dev, "Couldn't read \"rclk\" property from the DT\n");
+		return -EINVAL;
+	}
+
+	sn54hc595->oe = of_get_named_gpio_flags(np, "oe-gpios", 0, NULL);
+	if (sn54hc595->oe < 0) {
+		dev_err(dev, "Couldn't read \"oe\" property from the DT\n");
+		return -EINVAL;
+	}
+
+	if (devm_gpio_request(dev, sn54hc595->ser, dev_name(dev))) {
+		dev_err(dev, "Couldn't request GPIO %u\n", sn54hc595->ser);
+		return -EBUSY;
+	}
+
+	if (devm_gpio_request(dev, sn54hc595->srclk, dev_name(dev))) {
+		dev_err(dev, "Couldn't request GPIO %u\n", sn54hc595->srclk);
+		return -EBUSY;
+	}
+
+	if (devm_gpio_request(dev, sn54hc595->srclr, dev_name(dev))) {
+		dev_err(dev, "Couldn't request GPIO %u\n", sn54hc595->srclr);
+		return -EBUSY;
+	}
+
+	if (devm_gpio_request(dev, sn54hc595->rclk, dev_name(dev))) {
+		dev_err(dev, "Couldn't request GPIO %u\n", sn54hc595->rclk);
+		return -EBUSY;
+	}
+
+	if (devm_gpio_request(dev, sn54hc595->oe, dev_name(dev))) {
+		dev_err(dev, "Couldn't request GPIO %u\n", sn54hc595->oe);
+		return -EBUSY;
+	}
+
+	/* Get ready for raising shift clock and shifting values */
+	if (gpio_direction_output(sn54hc595->srclk, 0)) {
+		dev_err(dev, "Couldn't set \"srclk\" GPIO directon\n");
+		return -EIO;
+	}
+
+	/* Get ready for raising storage clock and load (copy) data */
+	if (gpio_direction_output(sn54hc595->rclk, 0)) {
+		dev_err(dev, "Couldn't set \"rclk\" GPIO directon\n");
+		return -EIO;
+	}
+
+	/* Enable outputs */
+	if (gpio_direction_output(sn54hc595->oe, 0)) {
+		dev_err(dev, "Couldn't set \"oe\" GPIO directon\n");
+		return -EIO;
+	}
+
+	/* Don't clear shift register */
+	if (gpio_direction_output(sn54hc595->srclr, 1)) {
+		dev_err(dev, "Couldn't set \"srclr\" GPIO directon\n");
+		return -EIO;
+	}
+
+	sn54hc595->gpio_chip.dev = dev;
+	sn54hc595->gpio_chip.label = "sn54hc595";
+	sn54hc595->gpio_chip.direction_input = sn54hc595_direction_input;
+	sn54hc595->gpio_chip.direction_output = sn54hc595_direction_output;
+	sn54hc595->gpio_chip.get = sn54hc595_get_value;
+	sn54hc595->gpio_chip.set = sn54hc595_set_value;
+	sn54hc595->gpio_chip.base = -1;
+	sn54hc595->gpio_chip.ngpio = SN54HC595_NUM_GPIOS;
+
+	err = gpiochip_add(&sn54hc595->gpio_chip);
+	if (err)
+		return err;
+
+	platform_set_drvdata(pdev, sn54hc595);
+
+	return 0;
+}
+
+static int sn54hc595_remove(struct platform_device *pdev)
+{
+	struct sn54hc595 *sn54hc595 = platform_get_drvdata(pdev);
+
+	gpiochip_remove(&sn54hc595->gpio_chip);
+
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+
+static const struct of_device_id sn54hc595_of_match[] = {
+	{ .compatible = "ti,sn54hc595", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, bcma_host_soc_of_match);
+
+static struct platform_driver sn54hc595_driver = {
+	.probe		= sn54hc595_probe,
+	.remove		= sn54hc595_remove,
+	.driver		= {
+		.name	= "sn54hc595",
+		.of_match_table = sn54hc595_of_match,
+	},
+};
+
+module_platform_driver(sn54hc595_driver);
+
+MODULE_AUTHOR("Rafał Miłecki");
+MODULE_LICENSE("GPL");
-- 
1.8.4.5

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux