Last active
November 3, 2023 06:56
-
-
Save erichschroeter/3660064 to your computer and use it in GitHub Desktop.
Linux driver for 74x165 PISO chip.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef LINUX_SPI_74X165_H | |
#define LINUX_SPI_74X165_H | |
struct nxp_74x165_chip_platform_data { | |
/* number assigned to the first GPIO */ | |
unsigned base; | |
/* GPIO used for latching chip(s) */ | |
unsigned latch; | |
/* number of chips daisy chained */ | |
unsigned daisy_chained; | |
}; | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 74Hx165 - Generic parallel-out/serial-in 8-bits shift register GPIO driver | |
* | |
* Copyright (C) 2012 Erich Schroeter <eschroeter@magnetek.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/module.h> | |
#include <linux/fs.h> | |
#include <linux/device.h> | |
#include <linux/mutex.h> | |
#include <linux/slab.h> | |
#include <linux/cdev.h> | |
#include <linux/string.h> | |
#include <linux/moduleparam.h> | |
#include <linux/gpio.h> | |
#include <linux/spi/spi.h> | |
#include <linux/spi/74x165.h> | |
#include <asm/uaccess.h> | |
#define DEFAULT_DAISY_CHAINED 1 | |
#define USER_BUFF_SIZE 128 | |
static int registered_by_this_driver = 0; | |
const char this_driver_name[] = "74x165"; | |
struct nxp_74x165_chip { | |
struct spi_device *spi_device; | |
struct gpio_chip gpio_chip; | |
struct mutex lock; | |
u8 *rx_buff; | |
u32 latch; | |
u32 daisy_chained; | |
dev_t devt; | |
struct cdev cdev; | |
struct class *class; | |
char *user_buff; | |
}; | |
static int nxp_74x165_read_spi(struct nxp_74x165_chip *chip) | |
{ | |
int status; | |
/* toggle latch to capture inputs */ | |
gpio_set_value(chip->latch, 0); | |
gpio_set_value(chip->latch, 1); | |
mutex_lock(&chip->lock); | |
if (chip->spi_device) | |
status = spi_read(chip->spi_device, chip->rx_buff, | |
chip->daisy_chained); | |
else | |
status = -ENODEV; | |
mutex_unlock(&chip->lock); | |
return status; | |
} | |
static ssize_t nxp_74x165_show_value(struct device *dev, | |
struct device_attribute *attr, | |
char *buf) | |
{ | |
struct nxp_74x165_chip *chip = dev_get_drvdata(dev); | |
int i; | |
int j; | |
nxp_74x165_read_spi(chip); | |
for (i = j = 0; i < chip->daisy_chained; ++i) { | |
j += sprintf(buf + j, "%02x", chip->rx_buff[i]); | |
} | |
sprintf(buf + j, "\n"); | |
return strlen(buf); | |
} | |
static DEVICE_ATTR(value, S_IRUGO, nxp_74x165_show_value, NULL); | |
static struct device_attribute *dev_attrs[] = { | |
&dev_attr_value, | |
}; | |
static int nxp_74x165_get_value(struct gpio_chip *gc, unsigned offset) | |
{ | |
struct nxp_74x165_chip *chip = container_of(gc, struct nxp_74x165_chip, gpio_chip); | |
int ret = 0; | |
int reg; | |
int off; | |
nxp_74x165_read_spi(chip); | |
/* figure out what shift register the bit is located, then the bit */ | |
reg = offset / 8; | |
off = offset % 8; | |
ret = (chip->rx_buff[reg] >> off) & 0x1; | |
return ret; | |
} | |
static int nxp_74x165_direction_input(struct gpio_chip *gc, | |
unsigned offset) | |
{ | |
nxp_74x165_get_value(gc, offset); | |
return 0; | |
} | |
static int __devinit nxp_74x165_probe(struct spi_device *spi) | |
{ | |
struct nxp_74x165_chip_platform_data *pdata; | |
struct nxp_74x165_chip *chip; | |
int ret = 0; | |
int ptr; | |
pdata = spi->dev.platform_data; | |
if (!pdata || !pdata->base || !pdata->latch) { | |
dev_dbg(&spi->dev, "incorrect or missing platform data\n"); | |
return -EINVAL; | |
} | |
if (!pdata->daisy_chained) { | |
pr_alert("%s : daisy_chained reset to %d\n", | |
this_driver_name, DEFAULT_DAISY_CHAINED); | |
pdata->daisy_chained = DEFAULT_DAISY_CHAINED; | |
} | |
chip = kzalloc(sizeof(*chip), GFP_KERNEL); | |
if (!chip) { | |
ret = -ENOMEM; | |
goto fail_chip; | |
} | |
spi_set_drvdata(spi, chip); | |
mutex_init(&chip->lock); | |
chip->daisy_chained = pdata->daisy_chained; | |
chip->latch = pdata->latch; | |
chip->spi_device = spi; | |
chip->rx_buff = kmalloc(chip->daisy_chained, GFP_KERNEL | GFP_DMA); | |
if (!chip->rx_buff) { | |
ret = -ENOMEM; | |
goto fail_rx_buff; | |
} | |
ret = gpio_request(chip->latch, "74x165-latch"); | |
if (ret < 0 && !registered_by_this_driver) { | |
dev_dbg(&spi->dev, "latch not available\n"); | |
goto fail_gpio_request; | |
} | |
registered_by_this_driver = 1; | |
gpio_direction_output(chip->latch, 0); | |
chip->gpio_chip.label = spi->modalias; | |
chip->gpio_chip.direction_input = nxp_74x165_direction_input; | |
chip->gpio_chip.get = nxp_74x165_get_value; | |
chip->gpio_chip.base = pdata->base; | |
chip->gpio_chip.ngpio = chip->daisy_chained * 8; | |
chip->gpio_chip.can_sleep = 1; | |
chip->gpio_chip.dev = &spi->dev; | |
chip->gpio_chip.owner = THIS_MODULE; | |
ret = gpiochip_add(&chip->gpio_chip); | |
if (ret) { | |
dev_dbg(&spi->dev, "Failed adding GPIO chip\n"); | |
goto fail_gpio_add; | |
} | |
ret = nxp_74x165_read_spi(chip); | |
if (ret) { | |
dev_err(&spi->dev, "Failed reading: %d\n", ret); | |
goto fail_read; | |
} | |
for (ptr = 0; ptr < ARRAY_SIZE(dev_attrs); ptr++) { | |
ret = device_create_file(&spi->dev, dev_attrs[ptr]); | |
if (ret) { | |
dev_err(&spi->dev, "cannot create device attribute\n"); | |
goto fail_attributes; | |
} | |
} | |
return ret; | |
fail_attributes: | |
while (--ptr >= 0) | |
device_remove_file(&spi->dev, dev_attrs[ptr]); | |
device_remove_file(&spi->dev, &dev_attr_value); | |
fail_read: | |
if (gpiochip_remove(&chip->gpio_chip)) | |
dev_dbg(&spi->dev, "could not remove gpio chip\n"); | |
fail_gpio_add: | |
gpio_free(chip->latch); | |
fail_gpio_request: | |
kfree(chip->rx_buff); | |
chip->rx_buff = 0; | |
fail_rx_buff: | |
spi_set_drvdata(spi, NULL); | |
kfree(chip); | |
fail_chip: | |
return ret; | |
} | |
static int __devexit nxp_74x165_remove(struct spi_device *spi) | |
{ | |
struct nxp_74x165_chip *chip; | |
int ret; | |
int ptr; | |
chip = dev_get_drvdata(&spi->dev); | |
if (chip == NULL) | |
return -ENODEV; | |
dev_set_drvdata(&spi->dev, NULL); | |
chip->spi_device = NULL; | |
for (ptr = 0; ptr < ARRAY_SIZE(dev_attrs); ptr++) | |
device_remove_file(&spi->dev, dev_attrs[ptr]); | |
device_remove_file(&spi->dev, &dev_attr_value); | |
gpio_free(chip->latch); | |
ret = gpiochip_remove(&chip->gpio_chip); | |
if (!ret) | |
kfree(chip); | |
else | |
dev_err(&spi->dev, "Failed to remove the GPIO controller: %d\n", | |
ret); | |
return ret; | |
} | |
static struct spi_driver nxp_74x165_spi_driver = { | |
.driver = { | |
.name = this_driver_name, | |
.owner = THIS_MODULE, | |
}, | |
.probe = nxp_74x165_probe, | |
.remove = __devexit_p(nxp_74x165_remove), | |
}; | |
static int __init nxp_74x165_init_spi(void) | |
{ | |
return spi_register_driver(&nxp_74x165_spi_driver); | |
} | |
static int __init nxp_74x165_init(void) | |
{ | |
int ret; | |
ret = nxp_74x165_init_spi(); | |
if (ret) | |
goto fail_spi_init; | |
return 0; | |
fail_spi_init: | |
return ret; | |
} | |
subsys_initcall(nxp_74x165_init); | |
static void __exit nxp_74x165_exit(void) | |
{ | |
spi_unregister_driver(&nxp_74x165_spi_driver); | |
} | |
module_exit(nxp_74x165_exit); | |
MODULE_AUTHOR("Erich Schroeter <eschroeter@magnetek.com>"); | |
MODULE_DESCRIPTION("GPIO expander driver for 74X165 8-bit shift register"); | |
MODULE_LICENSE("GPL"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment