forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
317 lines
7.0 KiB
317 lines
7.0 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Support Infineon TLE62x0 driver chips |
|
* |
|
* Copyright (c) 2007 Simtec Electronics |
|
* Ben Dooks, <[email protected]> |
|
*/ |
|
|
|
#include <linux/device.h> |
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/slab.h> |
|
|
|
#include <linux/spi/spi.h> |
|
#include <linux/spi/tle62x0.h> |
|
|
|
|
|
#define CMD_READ 0x00 |
|
#define CMD_SET 0xff |
|
|
|
#define DIAG_NORMAL 0x03 |
|
#define DIAG_OVERLOAD 0x02 |
|
#define DIAG_OPEN 0x01 |
|
#define DIAG_SHORTGND 0x00 |
|
|
|
struct tle62x0_state { |
|
struct spi_device *us; |
|
struct mutex lock; |
|
unsigned int nr_gpio; |
|
unsigned int gpio_state; |
|
|
|
unsigned char tx_buff[4]; |
|
unsigned char rx_buff[4]; |
|
}; |
|
|
|
static int to_gpio_num(struct device_attribute *attr); |
|
|
|
static inline int tle62x0_write(struct tle62x0_state *st) |
|
{ |
|
unsigned char *buff = st->tx_buff; |
|
unsigned int gpio_state = st->gpio_state; |
|
|
|
buff[0] = CMD_SET; |
|
|
|
if (st->nr_gpio == 16) { |
|
buff[1] = gpio_state >> 8; |
|
buff[2] = gpio_state; |
|
} else { |
|
buff[1] = gpio_state; |
|
} |
|
|
|
dev_dbg(&st->us->dev, "buff %3ph\n", buff); |
|
|
|
return spi_write(st->us, buff, (st->nr_gpio == 16) ? 3 : 2); |
|
} |
|
|
|
static inline int tle62x0_read(struct tle62x0_state *st) |
|
{ |
|
unsigned char *txbuff = st->tx_buff; |
|
struct spi_transfer xfer = { |
|
.tx_buf = txbuff, |
|
.rx_buf = st->rx_buff, |
|
.len = (st->nr_gpio * 2) / 8, |
|
}; |
|
struct spi_message msg; |
|
|
|
txbuff[0] = CMD_READ; |
|
txbuff[1] = 0x00; |
|
txbuff[2] = 0x00; |
|
txbuff[3] = 0x00; |
|
|
|
spi_message_init(&msg); |
|
spi_message_add_tail(&xfer, &msg); |
|
|
|
return spi_sync(st->us, &msg); |
|
} |
|
|
|
static unsigned char *decode_fault(unsigned int fault_code) |
|
{ |
|
fault_code &= 3; |
|
|
|
switch (fault_code) { |
|
case DIAG_NORMAL: |
|
return "N"; |
|
case DIAG_OVERLOAD: |
|
return "V"; |
|
case DIAG_OPEN: |
|
return "O"; |
|
case DIAG_SHORTGND: |
|
return "G"; |
|
} |
|
|
|
return "?"; |
|
} |
|
|
|
static ssize_t tle62x0_status_show(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct tle62x0_state *st = dev_get_drvdata(dev); |
|
char *bp = buf; |
|
unsigned char *buff = st->rx_buff; |
|
unsigned long fault = 0; |
|
int ptr; |
|
int ret; |
|
|
|
mutex_lock(&st->lock); |
|
ret = tle62x0_read(st); |
|
dev_dbg(dev, "tle62x0_read() returned %d\n", ret); |
|
if (ret < 0) { |
|
mutex_unlock(&st->lock); |
|
return ret; |
|
} |
|
|
|
for (ptr = 0; ptr < (st->nr_gpio * 2)/8; ptr += 1) { |
|
fault <<= 8; |
|
fault |= ((unsigned long)buff[ptr]); |
|
|
|
dev_dbg(dev, "byte %d is %02x\n", ptr, buff[ptr]); |
|
} |
|
|
|
for (ptr = 0; ptr < st->nr_gpio; ptr++) { |
|
bp += sprintf(bp, "%s ", decode_fault(fault >> (ptr * 2))); |
|
} |
|
|
|
*bp++ = '\n'; |
|
|
|
mutex_unlock(&st->lock); |
|
return bp - buf; |
|
} |
|
|
|
static DEVICE_ATTR(status_show, S_IRUGO, tle62x0_status_show, NULL); |
|
|
|
static ssize_t tle62x0_gpio_show(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct tle62x0_state *st = dev_get_drvdata(dev); |
|
int gpio_num = to_gpio_num(attr); |
|
int value; |
|
|
|
mutex_lock(&st->lock); |
|
value = (st->gpio_state >> gpio_num) & 1; |
|
mutex_unlock(&st->lock); |
|
|
|
return snprintf(buf, PAGE_SIZE, "%d", value); |
|
} |
|
|
|
static ssize_t tle62x0_gpio_store(struct device *dev, |
|
struct device_attribute *attr, |
|
const char *buf, size_t len) |
|
{ |
|
struct tle62x0_state *st = dev_get_drvdata(dev); |
|
int gpio_num = to_gpio_num(attr); |
|
unsigned long val; |
|
char *endp; |
|
|
|
val = simple_strtoul(buf, &endp, 0); |
|
if (buf == endp) |
|
return -EINVAL; |
|
|
|
dev_dbg(dev, "setting gpio %d to %ld\n", gpio_num, val); |
|
|
|
mutex_lock(&st->lock); |
|
|
|
if (val) |
|
st->gpio_state |= 1 << gpio_num; |
|
else |
|
st->gpio_state &= ~(1 << gpio_num); |
|
|
|
tle62x0_write(st); |
|
mutex_unlock(&st->lock); |
|
|
|
return len; |
|
} |
|
|
|
static DEVICE_ATTR(gpio1, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio2, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio3, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio4, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio5, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio6, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio7, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio8, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio9, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio10, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio11, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio12, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio13, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio14, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio15, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
static DEVICE_ATTR(gpio16, S_IWUSR|S_IRUGO, |
|
tle62x0_gpio_show, tle62x0_gpio_store); |
|
|
|
static struct device_attribute *gpio_attrs[] = { |
|
[0] = &dev_attr_gpio1, |
|
[1] = &dev_attr_gpio2, |
|
[2] = &dev_attr_gpio3, |
|
[3] = &dev_attr_gpio4, |
|
[4] = &dev_attr_gpio5, |
|
[5] = &dev_attr_gpio6, |
|
[6] = &dev_attr_gpio7, |
|
[7] = &dev_attr_gpio8, |
|
[8] = &dev_attr_gpio9, |
|
[9] = &dev_attr_gpio10, |
|
[10] = &dev_attr_gpio11, |
|
[11] = &dev_attr_gpio12, |
|
[12] = &dev_attr_gpio13, |
|
[13] = &dev_attr_gpio14, |
|
[14] = &dev_attr_gpio15, |
|
[15] = &dev_attr_gpio16 |
|
}; |
|
|
|
static int to_gpio_num(struct device_attribute *attr) |
|
{ |
|
int ptr; |
|
|
|
for (ptr = 0; ptr < ARRAY_SIZE(gpio_attrs); ptr++) { |
|
if (gpio_attrs[ptr] == attr) |
|
return ptr; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
static int tle62x0_probe(struct spi_device *spi) |
|
{ |
|
struct tle62x0_state *st; |
|
struct tle62x0_pdata *pdata; |
|
int ptr; |
|
int ret; |
|
|
|
pdata = dev_get_platdata(&spi->dev); |
|
if (pdata == NULL) { |
|
dev_err(&spi->dev, "no device data specified\n"); |
|
return -EINVAL; |
|
} |
|
|
|
st = kzalloc(sizeof(struct tle62x0_state), GFP_KERNEL); |
|
if (st == NULL) |
|
return -ENOMEM; |
|
|
|
st->us = spi; |
|
st->nr_gpio = pdata->gpio_count; |
|
st->gpio_state = pdata->init_state; |
|
|
|
mutex_init(&st->lock); |
|
|
|
ret = device_create_file(&spi->dev, &dev_attr_status_show); |
|
if (ret) { |
|
dev_err(&spi->dev, "cannot create status attribute\n"); |
|
goto err_status; |
|
} |
|
|
|
for (ptr = 0; ptr < pdata->gpio_count; ptr++) { |
|
ret = device_create_file(&spi->dev, gpio_attrs[ptr]); |
|
if (ret) { |
|
dev_err(&spi->dev, "cannot create gpio attribute\n"); |
|
goto err_gpios; |
|
} |
|
} |
|
|
|
/* tle62x0_write(st); */ |
|
spi_set_drvdata(spi, st); |
|
return 0; |
|
|
|
err_gpios: |
|
while (--ptr >= 0) |
|
device_remove_file(&spi->dev, gpio_attrs[ptr]); |
|
|
|
device_remove_file(&spi->dev, &dev_attr_status_show); |
|
|
|
err_status: |
|
kfree(st); |
|
return ret; |
|
} |
|
|
|
static int tle62x0_remove(struct spi_device *spi) |
|
{ |
|
struct tle62x0_state *st = spi_get_drvdata(spi); |
|
int ptr; |
|
|
|
for (ptr = 0; ptr < st->nr_gpio; ptr++) |
|
device_remove_file(&spi->dev, gpio_attrs[ptr]); |
|
|
|
device_remove_file(&spi->dev, &dev_attr_status_show); |
|
kfree(st); |
|
return 0; |
|
} |
|
|
|
static struct spi_driver tle62x0_driver = { |
|
.driver = { |
|
.name = "tle62x0", |
|
}, |
|
.probe = tle62x0_probe, |
|
.remove = tle62x0_remove, |
|
}; |
|
|
|
module_spi_driver(tle62x0_driver); |
|
|
|
MODULE_AUTHOR("Ben Dooks <[email protected]>"); |
|
MODULE_DESCRIPTION("TLE62x0 SPI driver"); |
|
MODULE_LICENSE("GPL v2"); |
|
MODULE_ALIAS("spi:tle62x0");
|
|
|