mirror of
https://github.com/torvalds/linux.git
synced 2024-11-01 17:51:43 +00:00
20bc4d5d56
The shift registers have an output pin that, when enabled, propagates the values of its internal register to the pins. If another value comes to the register while the output pin is disabled, this new value will makae the older shift into the next register in the chain. This patch adds support for daisy-chaining the registers, using the regular SPI chip select mechanism to manage the output pin, and the registers-number dt property to set the number of chained registers. Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
226 lines
5.3 KiB
C
226 lines
5.3 KiB
C
/*
|
|
* 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>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/spi/74x164.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/module.h>
|
|
|
|
#define GEN_74X164_NUMBER_GPIOS 8
|
|
|
|
struct gen_74x164_chip {
|
|
struct spi_device *spi;
|
|
u8 *buffer;
|
|
struct gpio_chip gpio_chip;
|
|
struct mutex lock;
|
|
u32 registers;
|
|
};
|
|
|
|
static struct gen_74x164_chip *gpio_to_74x164_chip(struct gpio_chip *gc)
|
|
{
|
|
return container_of(gc, struct gen_74x164_chip, gpio_chip);
|
|
}
|
|
|
|
static int __gen_74x164_write_config(struct gen_74x164_chip *chip)
|
|
{
|
|
struct spi_message message;
|
|
struct spi_transfer *msg_buf;
|
|
int i, ret = 0;
|
|
|
|
msg_buf = kzalloc(chip->registers * sizeof(struct spi_transfer),
|
|
GFP_KERNEL);
|
|
if (!msg_buf)
|
|
return -ENOMEM;
|
|
|
|
spi_message_init(&message);
|
|
|
|
/*
|
|
* 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 send will end up in the last
|
|
* register at the end of the transfer. So, to have a logical
|
|
* numbering, send the bytes in reverse order so that the last
|
|
* byte of the buffer will end up in the last register.
|
|
*/
|
|
for (i = chip->registers - 1; i >= 0; i--) {
|
|
msg_buf[i].tx_buf = chip->buffer +i;
|
|
msg_buf[i].len = sizeof(u8);
|
|
spi_message_add_tail(msg_buf + i, &message);
|
|
}
|
|
|
|
ret = spi_sync(chip->spi, &message);
|
|
|
|
kfree(msg_buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int gen_74x164_get_value(struct gpio_chip *gc, unsigned offset)
|
|
{
|
|
struct gen_74x164_chip *chip = gpio_to_74x164_chip(gc);
|
|
u8 bank = offset / 8;
|
|
u8 pin = offset % 8;
|
|
int ret;
|
|
|
|
mutex_lock(&chip->lock);
|
|
ret = (chip->buffer[bank] >> pin) & 0x1;
|
|
mutex_unlock(&chip->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void gen_74x164_set_value(struct gpio_chip *gc,
|
|
unsigned offset, int val)
|
|
{
|
|
struct gen_74x164_chip *chip = gpio_to_74x164_chip(gc);
|
|
u8 bank = offset / 8;
|
|
u8 pin = offset % 8;
|
|
|
|
mutex_lock(&chip->lock);
|
|
if (val)
|
|
chip->buffer[bank] |= (1 << pin);
|
|
else
|
|
chip->buffer[bank] &= ~(1 << pin);
|
|
|
|
__gen_74x164_write_config(chip);
|
|
mutex_unlock(&chip->lock);
|
|
}
|
|
|
|
static int gen_74x164_direction_output(struct gpio_chip *gc,
|
|
unsigned offset, int val)
|
|
{
|
|
gen_74x164_set_value(gc, offset, val);
|
|
return 0;
|
|
}
|
|
|
|
static int __devinit gen_74x164_probe(struct spi_device *spi)
|
|
{
|
|
struct gen_74x164_chip *chip;
|
|
struct gen_74x164_chip_platform_data *pdata;
|
|
int ret;
|
|
|
|
if (!spi->dev.of_node) {
|
|
dev_err(&spi->dev, "No device tree data available.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* bits_per_word cannot be configured in platform data
|
|
*/
|
|
spi->bits_per_word = 8;
|
|
|
|
ret = spi_setup(spi);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
chip = devm_kzalloc(&spi->dev, sizeof(*chip), GFP_KERNEL);
|
|
if (!chip)
|
|
return -ENOMEM;
|
|
|
|
pdata = spi->dev.platform_data;
|
|
if (pdata && pdata->base)
|
|
chip->gpio_chip.base = pdata->base;
|
|
else
|
|
chip->gpio_chip.base = -1;
|
|
|
|
mutex_init(&chip->lock);
|
|
|
|
dev_set_drvdata(&spi->dev, chip);
|
|
|
|
chip->spi = spi;
|
|
|
|
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;
|
|
|
|
if (of_property_read_u32(spi->dev.of_node, "registers-number", &chip->registers)) {
|
|
dev_err(&spi->dev, "Missing registers-number property in the DT.\n");
|
|
ret = -EINVAL;
|
|
goto exit_destroy;
|
|
}
|
|
|
|
chip->gpio_chip.ngpio = GEN_74X164_NUMBER_GPIOS * chip->registers;
|
|
chip->buffer = devm_kzalloc(&spi->dev, chip->gpio_chip.ngpio, GFP_KERNEL);
|
|
if (!chip->buffer) {
|
|
ret = -ENOMEM;
|
|
goto exit_destroy;
|
|
}
|
|
|
|
chip->gpio_chip.can_sleep = 1;
|
|
chip->gpio_chip.dev = &spi->dev;
|
|
chip->gpio_chip.owner = THIS_MODULE;
|
|
|
|
ret = __gen_74x164_write_config(chip);
|
|
if (ret) {
|
|
dev_err(&spi->dev, "Failed writing: %d\n", ret);
|
|
goto exit_destroy;
|
|
}
|
|
|
|
ret = gpiochip_add(&chip->gpio_chip);
|
|
if (ret)
|
|
goto exit_destroy;
|
|
|
|
return ret;
|
|
|
|
exit_destroy:
|
|
dev_set_drvdata(&spi->dev, NULL);
|
|
mutex_destroy(&chip->lock);
|
|
return ret;
|
|
}
|
|
|
|
static int __devexit gen_74x164_remove(struct spi_device *spi)
|
|
{
|
|
struct gen_74x164_chip *chip;
|
|
int ret;
|
|
|
|
chip = dev_get_drvdata(&spi->dev);
|
|
if (chip == NULL)
|
|
return -ENODEV;
|
|
|
|
dev_set_drvdata(&spi->dev, NULL);
|
|
|
|
ret = gpiochip_remove(&chip->gpio_chip);
|
|
if (!ret)
|
|
mutex_destroy(&chip->lock);
|
|
else
|
|
dev_err(&spi->dev, "Failed to remove the GPIO controller: %d\n",
|
|
ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct of_device_id gen_74x164_dt_ids[] = {
|
|
{ .compatible = "fairchild,74hc595" },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, gen_74x164_dt_ids);
|
|
|
|
static struct spi_driver gen_74x164_driver = {
|
|
.driver = {
|
|
.name = "74x164",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(gen_74x164_dt_ids),
|
|
},
|
|
.probe = gen_74x164_probe,
|
|
.remove = __devexit_p(gen_74x164_remove),
|
|
};
|
|
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");
|