436 lines
13 KiB
C
436 lines
13 KiB
C
/*
|
|
* SPDX-License-Identifier: GPL-2.0
|
|
* Copyright (c) 2023 MCST
|
|
*/
|
|
|
|
/*
|
|
* Linux kernel module for Texas Instruments 8-channel power-supply sequencer and monitor.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/init.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/mutex.h>
|
|
|
|
#define UCD9080_DRV_NAME "ucd9080"
|
|
#define DRIVER_VERSION "0.01"
|
|
|
|
/* RAIL registers: |--RAIL1H--|--RAIL1L--|...|--RAIL8L--|
|
|
* adjustment (+1) when reading RAIL1H - RAIL8H,
|
|
* or (-15) when reading RAIL8L */
|
|
#define UCD9080_RAIL_START_R_REG 0x00
|
|
#define UCD9080_RAILS_TOTAL 16
|
|
/* ERROR registers: |--ERROR1--|--ERROR2--|...|--ERROR6--| */
|
|
#define UCD9080_ERRORSTART_R_REG 0x20
|
|
/* Status: */
|
|
#define UCD9080_STATUS_R_REG 0x26
|
|
/* Version: */
|
|
#define UCD9080_VERSION_R_REG 0x27
|
|
/* Railstatus: |--RAILSTATUS2--|--RAILSTATUS1--| */
|
|
#define UCD9080_RAILSTATUS_START_R_REG 0x29
|
|
/* Flashlock (cachable): */
|
|
#define UCD9080_FLASHLOCK_RW_REG 0x2E
|
|
/* Restart: */
|
|
#define UCD9080_RESTART_W_REG 0x2F
|
|
/* Waddr (cachable): |--WADDR2--|--WADDR1--| */
|
|
#define UCD9080_WADDR_START_RW_REG 0x31
|
|
/* Wdata (cachable): |--WDATA2--|--WDATA1--| */
|
|
#define UCD9080_WDATA_START_RW_REG 0x33
|
|
|
|
/* Common */
|
|
/* Flashstates: */
|
|
#define UCD9080_FLASHSTATE_LOCK 0x00
|
|
#define UCD9080_FLASHSTATE_UNLOCK 0x02
|
|
|
|
/* Restart value: */
|
|
#define UCD9080_RESTART_VALUE 0x00
|
|
|
|
/* Configuration values */
|
|
#define UCD9080_CONF_WADDR_START 0xE000
|
|
#define UCD9080_CONF_WDATA_UPDATE 0xBADC
|
|
|
|
/* I2C Write block size (in bytes) */
|
|
#define UCD9080_I2C_WRITE_BLOCK_SIZE 32
|
|
#define UCD9080_I2C_READ_BLOCK_SIZE UCD9080_I2C_WRITE_BLOCK_SIZE
|
|
|
|
/* Total conf buffer size */
|
|
#define UCD9080_CONF_BUFFER_SIZE 512
|
|
#define UCD9080_CONF_ITERATIONS (UCD9080_CONF_BUFFER_SIZE / \
|
|
UCD9080_I2C_WRITE_BLOCK_SIZE)
|
|
|
|
/* Init sequence: ranges 0xE080-0xE0A8 and 0xE100-0xE188 are tunable */
|
|
static const unsigned char ucd9080_initseq[UCD9080_CONF_BUFFER_SIZE] = {
|
|
/* 0xE000 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE008 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE010 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE018 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE020 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE028 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE030 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE038 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE040 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE048 */ 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE050 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE058 */ 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x02,
|
|
/* 0xE060 */ 0x00, 0x00, 0x00, 0x0f, 0x00, 0x02, 0x00, 0x02,
|
|
/* 0xE068 */ 0xff, 0x0f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE070 */ 0x00, 0x00, 0xc0, 0x20, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE078 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0xdc, 0xba,
|
|
/* 0xE080 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
|
|
/* 0xE088 */ 0x00, 0x49, 0x4a, 0x4b, 0x01, 0x00, 0x01, 0x04,
|
|
/* 0xE090 */ 0x01, 0x04, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE098 */ 0x05, 0xe0, 0x05, 0xa0, 0x32, 0xe0, 0x33, 0xe0,
|
|
/* 0xE0A0 */ 0x33, 0xe0, 0x35, 0xe0, 0x35, 0xe0, 0x00, 0x00,
|
|
/* 0xE0A8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE0B0 */ 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f,
|
|
/* 0xE0B8 */ 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f,
|
|
/* 0xE0C0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE0C8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE0D0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE0D8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE0E0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE0E8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE0F0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE0F8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE100 */ 0x7f, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00,
|
|
/* 0xE108 */ 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00,
|
|
/* 0xE110 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE118 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE120 */ 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04,
|
|
/* 0xE128 */ 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04,
|
|
/* 0xE130 */ 0xa0, 0x0f, 0xa0, 0x0f, 0xa0, 0x0f, 0xa0, 0x0f,
|
|
/* 0xE138 */ 0xa0, 0x0f, 0xa0, 0x0f, 0xa0, 0x0f, 0xa0, 0x0f,
|
|
/* 0xE140 */ 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00,
|
|
/* 0xE148 */ 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00,
|
|
/* 0xE150 */ 0xff, 0xc0, 0xff, 0xc1, 0xff, 0xc2, 0xff, 0xc3,
|
|
/* 0xE158 */ 0xff, 0xc4, 0xff, 0xc5, 0xff, 0xc6, 0xff, 0xc7,
|
|
/* 0xE160 */ 0x00, 0x00, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0,
|
|
/* 0xE168 */ 0x04, 0x20, 0x08, 0x20, 0x04, 0x18, 0x02, 0x18,
|
|
/* 0xE170 */ 0x08, 0x18, 0x10, 0x18, 0x20, 0x18, 0x10, 0x20,
|
|
/* 0xE178 */ 0x00, 0x20, 0x20, 0x20, 0x40, 0x20, 0x80, 0x20,
|
|
/* 0xE180 */ 0x00, 0x00, 0x00, 0x04, 0x94, 0x02, 0xf2, 0x08,
|
|
/* 0xE188 */ 0x10, 0x03, 0x05, 0xc0, 0x40, 0x00, 0xff, 0x08,
|
|
/* 0xE190 */ 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE198 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE1A0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE1A8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE1B0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE1B8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE1C0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE1C8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE1D0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE1D8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE1E0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE1E8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE1F0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0xE1F8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
struct ucd9080_data {
|
|
struct i2c_client *client;
|
|
struct mutex lock;
|
|
int adapter_supports_blocks;
|
|
};
|
|
|
|
/*
|
|
* sysfs layer
|
|
*/
|
|
|
|
/* rail values */
|
|
static ssize_t ucd9080_show_railvalues(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
int len;
|
|
int value;
|
|
unsigned char railvalues[UCD9080_I2C_READ_BLOCK_SIZE];
|
|
struct ucd9080_data *data;
|
|
int i;
|
|
|
|
data = i2c_get_clientdata(client);
|
|
if (data->adapter_supports_blocks) {
|
|
len = i2c_smbus_read_block_data(client,
|
|
UCD9080_RAIL_START_R_REG,
|
|
railvalues);
|
|
if (len < 0)
|
|
return sprintf(buf, "Error\n");
|
|
} else { /* adapter does not support block reads */
|
|
len = 0;
|
|
for (i = 0; i < (UCD9080_RAILS_TOTAL>>1); i++) {
|
|
value = i2c_smbus_read_word_data(client,
|
|
UCD9080_RAIL_START_R_REG);
|
|
if (value < 0)
|
|
return sprintf(buf, "Error\n");
|
|
|
|
((unsigned short *)railvalues)[i] =
|
|
(unsigned short) value;
|
|
len += 2;
|
|
}
|
|
}
|
|
|
|
if (len != UCD9080_RAILS_TOTAL)
|
|
return sprintf(buf, "Error\n");
|
|
|
|
return sprintf(buf, "%i %i %i %i %i %i %i %i\n",
|
|
((unsigned short *)railvalues)[0],
|
|
((unsigned short *)railvalues)[1],
|
|
((unsigned short *)railvalues)[2],
|
|
((unsigned short *)railvalues)[3],
|
|
((unsigned short *)railvalues)[4],
|
|
((unsigned short *)railvalues)[5],
|
|
((unsigned short *)railvalues)[6],
|
|
((unsigned short *)railvalues)[7]);
|
|
}
|
|
static DEVICE_ATTR(railvalues, S_IRUSR, ucd9080_show_railvalues, NULL);
|
|
|
|
/* TODO: reading out errors FIFO buffer and parsing them */
|
|
|
|
/* status */
|
|
static ssize_t ucd9080_show_status(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
int status;
|
|
|
|
status = i2c_smbus_read_byte_data(client, UCD9080_STATUS_R_REG);
|
|
if (status < 0)
|
|
return sprintf(buf, "Error\n");
|
|
|
|
return sprintf(buf, "%i\n", status);
|
|
}
|
|
static DEVICE_ATTR(status, S_IRUSR, ucd9080_show_status, NULL);
|
|
|
|
/* version */
|
|
static ssize_t ucd9080_show_version(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
int version;
|
|
|
|
version = i2c_smbus_read_byte_data(client, UCD9080_VERSION_R_REG);
|
|
if (version < 0)
|
|
return sprintf(buf, "Error\n");
|
|
|
|
return sprintf(buf, "%i\n", version);
|
|
}
|
|
static DEVICE_ATTR(version, S_IRUSR, ucd9080_show_version, NULL);
|
|
|
|
/* railstatus */
|
|
static ssize_t ucd9080_show_railstatus(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
int railstatus;
|
|
|
|
railstatus = i2c_smbus_read_word_data(client,
|
|
UCD9080_RAILSTATUS_START_R_REG);
|
|
if (railstatus < 0)
|
|
return sprintf(buf, "Error\n");
|
|
|
|
return sprintf(buf, "%i\n", railstatus);
|
|
}
|
|
static DEVICE_ATTR(railstatus, S_IRUSR, ucd9080_show_railstatus, NULL);
|
|
|
|
/* restart */
|
|
static ssize_t ucd9080_store_restart(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
if ((kstrtoul(buf, 10, &val) < 0) || (val > 1))
|
|
return -EINVAL;
|
|
|
|
ret = i2c_smbus_write_byte_data(client, UCD9080_RESTART_W_REG,
|
|
UCD9080_RESTART_VALUE);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR(restart, S_IWUSR, NULL, ucd9080_store_restart);
|
|
|
|
static struct attribute *ucd9080_attributes[] = {
|
|
&dev_attr_railvalues.attr,
|
|
&dev_attr_status.attr,
|
|
&dev_attr_version.attr,
|
|
&dev_attr_railstatus.attr,
|
|
&dev_attr_restart.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group ucd9080_attr_group = {
|
|
.attrs = ucd9080_attributes,
|
|
};
|
|
|
|
static int ucd9080_init_chip(struct i2c_client *client)
|
|
{
|
|
struct ucd9080_data *data = i2c_get_clientdata(client);
|
|
int i, ii;
|
|
unsigned short waddr = UCD9080_WADDR_START_RW_REG;
|
|
unsigned char write_block[I2C_SMBUS_BLOCK_MAX + 2];
|
|
int err = 0;
|
|
|
|
/* Flashstate unlock */
|
|
err = i2c_smbus_write_byte_data(client, UCD9080_FLASHLOCK_RW_REG,
|
|
UCD9080_FLASHSTATE_UNLOCK);
|
|
if (err)
|
|
return err;
|
|
/* Write WADDR start */
|
|
err = i2c_smbus_write_word_data(client, UCD9080_WADDR_START_RW_REG,
|
|
UCD9080_CONF_WADDR_START);
|
|
if (err)
|
|
return err;
|
|
/* Write WDATA memory update constant */
|
|
err = i2c_smbus_write_word_data(client, UCD9080_WDATA_START_RW_REG,
|
|
UCD9080_CONF_WDATA_UPDATE);
|
|
if (err)
|
|
return err;
|
|
|
|
for (i = 0; i < UCD9080_CONF_ITERATIONS; i++) {
|
|
/* WADDR */
|
|
err = i2c_smbus_write_word_data(client,
|
|
UCD9080_WADDR_START_RW_REG,
|
|
waddr);
|
|
if (err)
|
|
return err;
|
|
|
|
if (data->adapter_supports_blocks) {
|
|
/* WDATA (block) */
|
|
write_block[0] = UCD9080_I2C_WRITE_BLOCK_SIZE;
|
|
memcpy(&write_block[1],
|
|
(ucd9080_initseq +
|
|
(i*UCD9080_I2C_WRITE_BLOCK_SIZE)),
|
|
UCD9080_I2C_WRITE_BLOCK_SIZE);
|
|
err = i2c_smbus_write_block_data(client,
|
|
UCD9080_WDATA_START_RW_REG,
|
|
UCD9080_I2C_WRITE_BLOCK_SIZE,
|
|
write_block);
|
|
if (err)
|
|
return err;
|
|
} else { /* adapter supports word only */
|
|
for (ii = 0; ii < (UCD9080_I2C_WRITE_BLOCK_SIZE>>1);
|
|
ii += 2) {
|
|
err = i2c_smbus_write_word_data(client,
|
|
UCD9080_WDATA_START_RW_REG,
|
|
((unsigned short *)ucd9080_initseq)[ii]);
|
|
if (err)
|
|
return err;
|
|
}
|
|
}
|
|
|
|
waddr += UCD9080_I2C_WRITE_BLOCK_SIZE;
|
|
}
|
|
/* Flashstate lock */
|
|
err = i2c_smbus_write_byte_data(client, UCD9080_FLASHLOCK_RW_REG,
|
|
UCD9080_FLASHSTATE_LOCK);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* I2C layer
|
|
*/
|
|
|
|
static int ucd9080_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
|
|
struct ucd9080_data *data;
|
|
int err = 0;
|
|
int adapter_supports_blocks = 1;
|
|
|
|
if (!i2c_check_functionality(adapter, (I2C_FUNC_SMBUS_BYTE |
|
|
I2C_FUNC_SMBUS_BLOCK_DATA))) {
|
|
adapter_supports_blocks = 0;
|
|
if (!i2c_check_functionality(adapter, (I2C_FUNC_SMBUS_BYTE |
|
|
I2C_FUNC_SMBUS_WORD_DATA))) {
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
data = kzalloc(sizeof(struct ucd9080_data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
data->client = client;
|
|
data->adapter_supports_blocks = adapter_supports_blocks;
|
|
i2c_set_clientdata(client, data);
|
|
mutex_init(&data->lock);
|
|
|
|
/* initialization of UCD9080 chip */
|
|
err = ucd9080_init_chip(client);
|
|
if (err)
|
|
goto exit_kfree;
|
|
|
|
/* register sysfs hooks */
|
|
err = sysfs_create_group(&client->dev.kobj, &ucd9080_attr_group);
|
|
if (err)
|
|
goto exit_kfree;
|
|
|
|
dev_info(&client->dev, "driver version %s enable\n", DRIVER_VERSION);
|
|
return 0;
|
|
|
|
exit_kfree:
|
|
kfree(data);
|
|
return err;
|
|
}
|
|
|
|
static int ucd9080_remove(struct i2c_client *client)
|
|
{
|
|
struct ucd9080_data *data;
|
|
|
|
data = i2c_get_clientdata(client);
|
|
if (data) {
|
|
sysfs_remove_group(&client->dev.kobj, &ucd9080_attr_group);
|
|
kfree(data);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_MCST
|
|
static const struct of_device_id ucd9080_of_match[] = {
|
|
{ .compatible = "ti,ucd9080" },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, ucd9080_of_match);
|
|
#endif
|
|
|
|
static const struct i2c_device_id ucd9080_id[] = {
|
|
{ "ucd9080", 0},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, ucd9080_id);
|
|
|
|
static struct i2c_driver ucd9080_driver = {
|
|
.driver = {
|
|
.name = UCD9080_DRV_NAME,
|
|
#ifdef CONFIG_MCST
|
|
.of_match_table = of_match_ptr(ucd9080_of_match),
|
|
#endif
|
|
.owner = THIS_MODULE,
|
|
},
|
|
.probe = ucd9080_probe,
|
|
.remove = ucd9080_remove,
|
|
.id_table = ucd9080_id,
|
|
};
|
|
|
|
static int __init ucd9080_init(void)
|
|
{
|
|
return i2c_add_driver(&ucd9080_driver);
|
|
}
|
|
|
|
static void __exit ucd9080_exit(void)
|
|
{
|
|
i2c_del_driver(&ucd9080_driver);
|
|
}
|
|
|
|
MODULE_AUTHOR("MCST");
|
|
MODULE_DESCRIPTION("TI 8-channel power-supply sequencer and monitor");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_VERSION(DRIVER_VERSION);
|
|
|
|
module_init(ucd9080_init);
|
|
module_exit(ucd9080_exit);
|