Message ID | 20201230155440.15721-1-lancelot.kao@fii-usa.com |
---|---|
State | New |
Headers | show |
Series | [linux,dev-5.8] Support Hwmon ADM1266 Driver | expand |
On Wed, 30 Dec 2020 at 15:55, Lancelot Kao <lancelot.kao@fii-usa.com> wrote: > > From: mustafa <mustafa.shehabi@fii-na.com> > > This hwmon/pmbus driver supports ADM1266 device > It modified from linux kernel 5.10 and Fii modified > it to fit openbmc kernel 5.8 > > Signed-off-by: Mustafa Shehabi <Mustafa.shehabi@fii-na.com> I've merged this to dev-5.8 with a slightly modified commit message. Thanks for doing the backporting. Cheers, Joel > --- > .../bindings/hwmon/adi,adm1266.yaml | 51 ++ > Documentation/hwmon/adm1266.rst | 37 ++ > Documentation/hwmon/index.rst | 1 + > drivers/hwmon/pmbus/Kconfig | 11 + > drivers/hwmon/pmbus/Makefile | 1 + > drivers/hwmon/pmbus/adm1266.c | 513 ++++++++++++++++++ > 6 files changed, 614 insertions(+) > create mode 100644 Documentation/devicetree/bindings/hwmon/adi,adm1266.yaml > create mode 100644 Documentation/hwmon/adm1266.rst > create mode 100644 drivers/hwmon/pmbus/adm1266.c > > diff --git a/Documentation/devicetree/bindings/hwmon/adi,adm1266.yaml b/Documentation/devicetree/bindings/hwmon/adi,adm1266.yaml > new file mode 100644 > index 000000000000..d088c744d53a > --- /dev/null > +++ b/Documentation/devicetree/bindings/hwmon/adi,adm1266.yaml > @@ -0,0 +1,51 @@ > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/hwmon/adi,adm1266.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: Analog Devices ADM1266 Cascadable Super Sequencer with Margin > + Control and Fault Recording > + > +maintainers: > + - Alexandru Tachici <alexandru.tachici@analog.com> > + > +description: | > + Analog Devices ADM1266 Cascadable Super Sequencer with Margin > + Control and Fault Recording. > + https://www.analog.com/media/en/technical-documentation/d... > + > +properties: > + compatible: > + enum: > + - adi,adm1266 > + > + reg: > + description: | > + I2C address of slave device. > + items: > + minimum: 0x40 > + maximum: 0x4F > + > + avcc-supply: > + description: | > + Phandle to the Avcc power supply. > + > +required: > + - compatible > + - reg > + > +additionalProperties: false > + > +examples: > + - | > + i2c0 { > + #address-cells = <1>; > + #size-cells = <0>; > + > + adm1266@40 { > + compatible = "adi,adm1266"; > + reg = <0x40>; > + }; > + }; > +... > diff --git a/Documentation/hwmon/adm1266.rst b/Documentation/hwmon/adm1266.rst > new file mode 100644 > index 000000000000..c7e5b0fab381 > --- /dev/null > +++ b/Documentation/hwmon/adm1266.rst > @@ -0,0 +1,37 @@ > +.. SPDX-License-Identifier: GPL-2.0 > + > +Kernel driver adm1266 > +===================== > + > +Supported chips: > + * Analog Devices ADM1266 > + Prefix: 'adm1266' > + Datasheet: https://www.analog.com/media/en/technical-documentation/d... > + > +Author: Alexandru Tachici <alexandru.tachici@analog.com> > + > + > +Description > +----------- > + > +This driver supports hardware monitoring for Analog Devices ADM1266 sequencer. > + > +ADM1266 is a sequencer that features voltage readback from 17 channels via an > +integrated 12 bit SAR ADC, accessed using a PMBus interface. > + > +The driver is a client driver to the core PMBus driver. Please see > +Documentation/hwmon/pmbus for details on PMBus client drivers. > + > + > +Sysfs entries > +------------- > + > +The following attributes are supported. Limits are read-write, history reset > +attributes are write-only, all other attributes are read-only. > + > +inX_label "voutx" > +inX_input Measured voltage. > +inX_min Minimum Voltage. > +inX_max Maximum voltage. > +inX_min_alarm Voltage low alarm. > +inX_max_alarm Voltage high alarm. > diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst > index 89e1a824021f..37e1925735c7 100644 > --- a/Documentation/hwmon/index.rst > +++ b/Documentation/hwmon/index.rst > @@ -30,6 +30,7 @@ Hardware Monitoring Kernel Drivers > adm1026 > adm1031 > adm1177 > + adm1266 > adm1275 > adm9240 > ads7828 > diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig > index ea516cec1d35..63b9d1ec200b 100644 > --- a/drivers/hwmon/pmbus/Kconfig > +++ b/drivers/hwmon/pmbus/Kconfig > @@ -26,6 +26,17 @@ config SENSORS_PMBUS > This driver can also be built as a module. If so, the module will > be called pmbus. > > +config SENSORS_ADM1266 > + tristate "Analog Devices ADM1266 Sequencer" > + select CRC8 > + depends on GPIOLIB > + help > + If you say yes here you get hardware monitoring support for Analog > + Devices ADM1266 Cascadable Super Sequencer. > + > + This driver can also be built as a module. If so, the module will > + be called adm1266. > + > config SENSORS_ADM1275 > tristate "Analog Devices ADM1275 and compatibles" > help > diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile > index c4b15db996ad..da41d22be1c9 100644 > --- a/drivers/hwmon/pmbus/Makefile > +++ b/drivers/hwmon/pmbus/Makefile > @@ -5,6 +5,7 @@ > > obj-$(CONFIG_PMBUS) += pmbus_core.o > obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o > +obj-$(CONFIG_SENSORS_ADM1266) += adm1266.o > obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o > obj-$(CONFIG_SENSORS_BEL_PFE) += bel-pfe.o > obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o > diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c > new file mode 100644 > index 000000000000..1d83c4f42eaf > --- /dev/null > +++ b/drivers/hwmon/pmbus/adm1266.c > @@ -0,0 +1,513 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * ADM1266 - Cascadable Super Sequencer with Margin > + * Control and Fault Recording > + * > + * Copyright 2020 Analog Devices Inc. > + */ > + > +#include <linux/bitfield.h> > +#include <linux/crc8.h> > +#include <linux/debugfs.h> > +#include <linux/gpio/driver.h> > +#include <linux/i2c.h> > +#include <linux/i2c-smbus.h> > +#include <linux/init.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/nvmem-consumer.h> > +#include <linux/nvmem-provider.h> > +#include "pmbus.h" > +#include <linux/slab.h> > +#include <linux/timekeeping.h> > + > +#define ADM1266_BLACKBOX_CONFIG 0xD3 > +#define ADM1266_PDIO_CONFIG 0xD4 > +#define ADM1266_READ_STATE 0xD9 > +#define ADM1266_READ_BLACKBOX 0xDE > +#define ADM1266_SET_RTC 0xDF > +#define ADM1266_GPIO_CONFIG 0xE1 > +#define ADM1266_BLACKBOX_INFO 0xE6 > +#define ADM1266_PDIO_STATUS 0xE9 > +#define ADM1266_GPIO_STATUS 0xEA > + > +/* ADM1266 GPIO defines */ > +#define ADM1266_GPIO_NR 9 > +#define ADM1266_GPIO_FUNCTIONS(x) FIELD_GET(BIT(0), x) > +#define ADM1266_GPIO_INPUT_EN(x) FIELD_GET(BIT(2), x) > +#define ADM1266_GPIO_OUTPUT_EN(x) FIELD_GET(BIT(3), x) > +#define ADM1266_GPIO_OPEN_DRAIN(x) FIELD_GET(BIT(4), x) > + > +/* ADM1266 PDIO defines */ > +#define ADM1266_PDIO_NR 16 > +#define ADM1266_PDIO_PIN_CFG(x) FIELD_GET(GENMASK(15, 13), x) > +#define ADM1266_PDIO_GLITCH_FILT(x) FIELD_GET(GENMASK(12, 9), x) > +#define ADM1266_PDIO_OUT_CFG(x) FIELD_GET(GENMASK(2, 0), x) > + > +#define ADM1266_BLACKBOX_OFFSET 0 > +#define ADM1266_BLACKBOX_SIZE 64 > + > +#define ADM1266_PMBUS_BLOCK_MAX 255 > + > +struct adm1266_data { > + struct pmbus_driver_info info; > + struct gpio_chip gc; > + const char *gpio_names[ADM1266_GPIO_NR + ADM1266_PDIO_NR]; > + struct i2c_client *client; > + struct dentry *debugfs_dir; > + struct nvmem_config nvmem_config; > + struct nvmem_device *nvmem; > + u8 *dev_mem; > + struct mutex buf_mutex; > + u8 write_buf[ADM1266_PMBUS_BLOCK_MAX + 1] ____cacheline_aligned; > + u8 read_buf[ADM1266_PMBUS_BLOCK_MAX + 1] ____cacheline_aligned; > +}; > + > +static const struct nvmem_cell_info adm1266_nvmem_cells[] = { > + { > + .name = "blackbox", > + .offset = ADM1266_BLACKBOX_OFFSET, > + .bytes = 2048, > + }, > +}; > + > +DECLARE_CRC8_TABLE(pmbus_crc_table); > + > +/* > + * Different from Block Read as it sends data and waits for the slave to > + * return a value dependent on that data. The protocol is simply a Write Block > + * followed by a Read Block without the Read-Block command field and the > + * Write-Block STOP bit. > + */ > +static int adm1266_pmbus_block_xfer(struct adm1266_data *data, u8 cmd, u8 w_len, u8 *data_w, > + u8 *data_r) > +{ > + struct i2c_client *client = data->client; > + struct i2c_msg msgs[2] = { > + { > + .addr = client->addr, > + .flags = I2C_M_DMA_SAFE, > + .buf = data->write_buf, > + .len = w_len + 2, > + }, > + { > + .addr = client->addr, > + .flags = I2C_M_RD | I2C_M_DMA_SAFE, > + .buf = data->read_buf, > + .len = ADM1266_PMBUS_BLOCK_MAX + 2, > + } > + }; > + u8 addr; > + u8 crc; > + int ret; > + > + mutex_lock(&data->buf_mutex); > + > + msgs[0].buf[0] = cmd; > + msgs[0].buf[1] = w_len; > + memcpy(&msgs[0].buf[2], data_w, w_len); > + > + ret = i2c_transfer(client->adapter, msgs, 2); > + if (ret != 2) { > + if (ret >= 0) > + ret = -EPROTO; > + > + mutex_unlock(&data->buf_mutex); > + > + return ret; > + } > + > + if (client->flags & I2C_CLIENT_PEC) { > + addr = i2c_8bit_addr_from_msg(&msgs[0]); > + crc = crc8(pmbus_crc_table, &addr, 1, 0); > + crc = crc8(pmbus_crc_table, msgs[0].buf, msgs[0].len, crc); > + > + addr = i2c_8bit_addr_from_msg(&msgs[1]); > + crc = crc8(pmbus_crc_table, &addr, 1, crc); > + crc = crc8(pmbus_crc_table, msgs[1].buf, msgs[1].buf[0] + 1, crc); > + > + if (crc != msgs[1].buf[msgs[1].buf[0] + 1]) { > + mutex_unlock(&data->buf_mutex); > + return -EBADMSG; > + } > + } > + > + memcpy(data_r, &msgs[1].buf[1], msgs[1].buf[0]); > + > + ret = msgs[1].buf[0]; > + mutex_unlock(&data->buf_mutex); > + > + return ret; > +} > + > +static const unsigned int adm1266_gpio_mapping[ADM1266_GPIO_NR][2] = { > + {1, 0}, > + {2, 1}, > + {3, 2}, > + {4, 8}, > + {5, 9}, > + {6, 10}, > + {7, 11}, > + {8, 6}, > + {9, 7}, > +}; > + > +static const char *adm1266_names[ADM1266_GPIO_NR + ADM1266_PDIO_NR] = { > + "GPIO1", "GPIO2", "GPIO3", "GPIO4", "GPIO5", "GPIO6", "GPIO7", "GPIO8", > + "GPIO9", "PDIO1", "PDIO2", "PDIO3", "PDIO4", "PDIO5", "PDIO6", > + "PDIO7", "PDIO8", "PDIO9", "PDIO10", "PDIO11", "PDIO12", "PDIO13", > + "PDIO14", "PDIO15", "PDIO16", > +}; > + > +static int adm1266_gpio_get(struct gpio_chip *chip, unsigned int offset) > +{ > + struct adm1266_data *data = gpiochip_get_data(chip); > + u8 read_buf[I2C_SMBUS_BLOCK_MAX + 1]; > + unsigned long pins_status; > + unsigned int pmbus_cmd; > + int ret; > + > + if (offset < ADM1266_GPIO_NR) > + pmbus_cmd = ADM1266_GPIO_STATUS; > + else > + pmbus_cmd = ADM1266_PDIO_STATUS; > + > + ret = i2c_smbus_read_block_data(data->client, pmbus_cmd, read_buf); > + if (ret < 0) > + return ret; > + > + pins_status = read_buf[0] + (read_buf[1] << 8); > + if (offset < ADM1266_GPIO_NR) > + return test_bit(adm1266_gpio_mapping[offset][1], &pins_status); > + > + return test_bit(offset - ADM1266_GPIO_NR, &pins_status); > +} > + > +static int adm1266_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask, > + unsigned long *bits) > +{ > + struct adm1266_data *data = gpiochip_get_data(chip); > + u8 read_buf[ADM1266_PMBUS_BLOCK_MAX + 1]; > + unsigned long status; > + unsigned int gpio_nr; > + int ret; > + > + ret = i2c_smbus_read_block_data(data->client, ADM1266_GPIO_STATUS, read_buf); > + if (ret < 0) > + return ret; > + > + status = read_buf[0] + (read_buf[1] << 8); > + > + *bits = 0; > + for_each_set_bit(gpio_nr, mask, ADM1266_GPIO_NR) { > + if (test_bit(adm1266_gpio_mapping[gpio_nr][1], &status)) > + set_bit(gpio_nr, bits); > + } > + > + ret = i2c_smbus_read_block_data(data->client, ADM1266_PDIO_STATUS, read_buf); > + if (ret < 0) > + return ret; > + > + status = read_buf[0] + (read_buf[1] << 8); > + > + *bits = 0; > + for_each_set_bit_from(gpio_nr, mask, ADM1266_GPIO_NR + ADM1266_PDIO_STATUS) { > + if (test_bit(gpio_nr - ADM1266_GPIO_NR, &status)) > + set_bit(gpio_nr, bits); > + } > + > + return 0; > +} > + > +static void adm1266_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) > +{ > + struct adm1266_data *data = gpiochip_get_data(chip); > + u8 read_buf[ADM1266_PMBUS_BLOCK_MAX + 1]; > + unsigned long gpio_config; > + unsigned long pdio_config; > + unsigned long pin_cfg; > + u8 write_cmd; > + int ret; > + int i; > + > + for (i = 0; i < ADM1266_GPIO_NR; i++) { > + write_cmd = adm1266_gpio_mapping[i][1]; > + ret = adm1266_pmbus_block_xfer(data, ADM1266_GPIO_CONFIG, 1, &write_cmd, read_buf); > + if (ret != 2) > + return; > + > + gpio_config = read_buf[0]; > + seq_puts(s, adm1266_names[i]); > + > + seq_puts(s, " ( "); > + if (!ADM1266_GPIO_FUNCTIONS(gpio_config)) { > + seq_puts(s, "high-Z )\n"); > + continue; > + } > + if (ADM1266_GPIO_INPUT_EN(gpio_config)) > + seq_puts(s, "input "); > + if (ADM1266_GPIO_OUTPUT_EN(gpio_config)) > + seq_puts(s, "output "); > + if (ADM1266_GPIO_OPEN_DRAIN(gpio_config)) > + seq_puts(s, "open-drain )\n"); > + else > + seq_puts(s, "push-pull )\n"); > + } > + > + write_cmd = 0xFF; > + ret = adm1266_pmbus_block_xfer(data, ADM1266_PDIO_CONFIG, 1, &write_cmd, read_buf); > + if (ret != 32) > + return; > + > + for (i = 0; i < ADM1266_PDIO_NR; i++) { > + seq_puts(s, adm1266_names[ADM1266_GPIO_NR + i]); > + > + pdio_config = read_buf[2 * i]; > + pdio_config += (read_buf[2 * i + 1] << 8); > + pin_cfg = ADM1266_PDIO_PIN_CFG(pdio_config); > + > + seq_puts(s, " ( "); > + if (!pin_cfg || pin_cfg > 5) { > + seq_puts(s, "high-Z )\n"); > + continue; > + } > + > + if (pin_cfg & BIT(0)) > + seq_puts(s, "output "); > + > + if (pin_cfg & BIT(1)) > + seq_puts(s, "input "); > + > + seq_puts(s, ")\n"); > + } > +} > + > +static int adm1266_config_gpio(struct adm1266_data *data) > +{ > + const char *name = dev_name(&data->client->dev); > + char *gpio_name; > + int ret; > + int i; > + > + for (i = 0; i < ARRAY_SIZE(data->gpio_names); i++) { > + gpio_name = devm_kasprintf(&data->client->dev, GFP_KERNEL, "adm1266-%x-%s", > + data->client->addr, adm1266_names[i]); > + if (!gpio_name) > + return -ENOMEM; > + > + data->gpio_names[i] = gpio_name; > + } > + > + data->gc.label = name; > + data->gc.parent = &data->client->dev; > + data->gc.owner = THIS_MODULE; > + data->gc.base = -1; > + data->gc.names = data->gpio_names; > + data->gc.ngpio = ARRAY_SIZE(data->gpio_names); > + data->gc.get = adm1266_gpio_get; > + data->gc.get_multiple = adm1266_gpio_get_multiple; > + data->gc.dbg_show = adm1266_gpio_dbg_show; > + > + ret = devm_gpiochip_add_data(&data->client->dev, &data->gc, data); > + if (ret) > + dev_err(&data->client->dev, "GPIO registering failed (%d)\n", ret); > + > + return ret; > +} > + > +static int adm1266_state_read(struct seq_file *s, void *pdata) > +{ > + struct device *dev = s->private; > + struct i2c_client *client = to_i2c_client(dev); > + int ret; > + > + ret = i2c_smbus_read_word_data(client, ADM1266_READ_STATE); > + if (ret < 0) > + return ret; > + > + seq_printf(s, "%d\n", ret); > + > + return 0; > +} > + > +static void adm1266_init_debugfs(struct adm1266_data *data) > +{ > + struct dentry *root; > + > + root = pmbus_get_debugfs_dir(data->client); > + if (!root) > + return; > + > + data->debugfs_dir = debugfs_create_dir(data->client->name, root); > + if (!data->debugfs_dir) > + return; > + > + debugfs_create_devm_seqfile(&data->client->dev, "sequencer_state", data->debugfs_dir, > + adm1266_state_read); > +} > + > +static int adm1266_nvmem_read_blackbox(struct adm1266_data *data, u8 *read_buff) > +{ > + int record_count; > + char index; > + u8 buf[5]; > + int ret; > + > + ret = i2c_smbus_read_block_data(data->client, ADM1266_BLACKBOX_INFO, buf); > + if (ret < 0) > + return ret; > + > + if (ret != 4) > + return -EIO; > + > + record_count = buf[3]; > + > + for (index = 0; index < record_count; index++) { > + ret = adm1266_pmbus_block_xfer(data, ADM1266_READ_BLACKBOX, 1, &index, read_buff); > + if (ret < 0) > + return ret; > + > + if (ret != ADM1266_BLACKBOX_SIZE) > + return -EIO; > + > + read_buff += ADM1266_BLACKBOX_SIZE; > + } > + > + return 0; > +} > + > +static int adm1266_nvmem_read(void *priv, unsigned int offset, void *val, size_t bytes) > +{ > + struct adm1266_data *data = priv; > + int ret; > + > + if (offset + bytes > data->nvmem_config.size) > + return -EINVAL; > + > + if (offset == 0) { > + memset(data->dev_mem, 0, data->nvmem_config.size); > + > + ret = adm1266_nvmem_read_blackbox(data, data->dev_mem); > + if (ret) { > + dev_err(&data->client->dev, "Could not read blackbox!"); > + return ret; > + } > + } > + > + memcpy(val, data->dev_mem + offset, bytes); > + > + return 0; > +} > + > +static int adm1266_config_nvmem(struct adm1266_data *data) > +{ > + data->nvmem_config.name = dev_name(&data->client->dev); > + data->nvmem_config.dev = &data->client->dev; > + data->nvmem_config.root_only = true; > + data->nvmem_config.read_only = true; > + data->nvmem_config.owner = THIS_MODULE; > + data->nvmem_config.reg_read = adm1266_nvmem_read; > + data->nvmem_config.cells = adm1266_nvmem_cells; > + data->nvmem_config.ncells = ARRAY_SIZE(adm1266_nvmem_cells); > + data->nvmem_config.priv = data; > + data->nvmem_config.stride = 1; > + data->nvmem_config.word_size = 1; > + data->nvmem_config.size = adm1266_nvmem_cells[0].bytes; > + > + data->dev_mem = devm_kzalloc(&data->client->dev, data->nvmem_config.size, GFP_KERNEL); > + if (!data->dev_mem) > + return -ENOMEM; > + > + data->nvmem = devm_nvmem_register(&data->client->dev, &data->nvmem_config); > + if (IS_ERR(data->nvmem)) { > + dev_err(&data->client->dev, "Could not register nvmem!"); > + return PTR_ERR(data->nvmem); > + } > + > + return 0; > +} > + > +static int adm1266_set_rtc(struct adm1266_data *data) > +{ > + time64_t kt; > + char write_buf[6]; > + int i; > + > + kt = ktime_get_seconds(); > + > + memset(write_buf, 0, sizeof(write_buf)); > + > + for (i = 0; i < 4; i++) > + write_buf[2 + i] = (kt >> (i * 8)) & 0xFF; > + > + return i2c_smbus_write_block_data(data->client, ADM1266_SET_RTC, sizeof(write_buf), > + write_buf); > +} > + > +static int adm1266_probe(struct i2c_client *client, const struct i2c_device_id *id) > +{ > + struct adm1266_data *data; > + int ret; > + int i; > + > + data = devm_kzalloc(&client->dev, sizeof(struct adm1266_data), GFP_KERNEL); > + if (!data) > + return -ENOMEM; > + > + data->client = client; > + data->info.pages = 17; > + data->info.format[PSC_VOLTAGE_OUT] = linear; > + for (i = 0; i < data->info.pages; i++) > + data->info.func[i] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; > + > + crc8_populate_msb(pmbus_crc_table, 0x7); > + mutex_init(&data->buf_mutex); > + > + ret = adm1266_config_gpio(data); > + if (ret < 0) > + return ret; > + > + ret = adm1266_set_rtc(data); > + if (ret < 0) > + return ret; > + > + ret = adm1266_config_nvmem(data); > + if (ret < 0) > + return ret; > + > + ret = pmbus_do_probe(client, id, &data->info); > + if (ret) > + return ret; > + > + adm1266_init_debugfs(data); > + > + return 0; > +} > + > +static const struct of_device_id adm1266_of_match[] = { > + { .compatible = "adi,adm1266" }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, adm1266_of_match); > + > +static const struct i2c_device_id adm1266_id[] = { > + { "adm1266", 0 }, > + { } > +}; > +MODULE_DEVICE_TABLE(i2c, adm1266_id); > + > +static struct i2c_driver adm1266_driver = { > + .driver = { > + .name = "adm1266", > + .of_match_table = adm1266_of_match, > + }, > + .probe = adm1266_probe, > + .remove = pmbus_do_remove, > + .id_table = adm1266_id, > +}; > + > +module_i2c_driver(adm1266_driver); > + > +MODULE_AUTHOR("Alexandru Tachici <alexandru.tachici@analog.com>"); > +MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1266"); > +MODULE_LICENSE("GPL v2"); > -- > 2.17.1 >
diff --git a/Documentation/devicetree/bindings/hwmon/adi,adm1266.yaml b/Documentation/devicetree/bindings/hwmon/adi,adm1266.yaml new file mode 100644 index 000000000000..d088c744d53a --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/adi,adm1266.yaml @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/adi,adm1266.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices ADM1266 Cascadable Super Sequencer with Margin + Control and Fault Recording + +maintainers: + - Alexandru Tachici <alexandru.tachici@analog.com> + +description: | + Analog Devices ADM1266 Cascadable Super Sequencer with Margin + Control and Fault Recording. + https://www.analog.com/media/en/technical-documentation/d... + +properties: + compatible: + enum: + - adi,adm1266 + + reg: + description: | + I2C address of slave device. + items: + minimum: 0x40 + maximum: 0x4F + + avcc-supply: + description: | + Phandle to the Avcc power supply. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + adm1266@40 { + compatible = "adi,adm1266"; + reg = <0x40>; + }; + }; +... diff --git a/Documentation/hwmon/adm1266.rst b/Documentation/hwmon/adm1266.rst new file mode 100644 index 000000000000..c7e5b0fab381 --- /dev/null +++ b/Documentation/hwmon/adm1266.rst @@ -0,0 +1,37 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver adm1266 +===================== + +Supported chips: + * Analog Devices ADM1266 + Prefix: 'adm1266' + Datasheet: https://www.analog.com/media/en/technical-documentation/d... + +Author: Alexandru Tachici <alexandru.tachici@analog.com> + + +Description +----------- + +This driver supports hardware monitoring for Analog Devices ADM1266 sequencer. + +ADM1266 is a sequencer that features voltage readback from 17 channels via an +integrated 12 bit SAR ADC, accessed using a PMBus interface. + +The driver is a client driver to the core PMBus driver. Please see +Documentation/hwmon/pmbus for details on PMBus client drivers. + + +Sysfs entries +------------- + +The following attributes are supported. Limits are read-write, history reset +attributes are write-only, all other attributes are read-only. + +inX_label "voutx" +inX_input Measured voltage. +inX_min Minimum Voltage. +inX_max Maximum voltage. +inX_min_alarm Voltage low alarm. +inX_max_alarm Voltage high alarm. diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 89e1a824021f..37e1925735c7 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -30,6 +30,7 @@ Hardware Monitoring Kernel Drivers adm1026 adm1031 adm1177 + adm1266 adm1275 adm9240 ads7828 diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index ea516cec1d35..63b9d1ec200b 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -26,6 +26,17 @@ config SENSORS_PMBUS This driver can also be built as a module. If so, the module will be called pmbus. +config SENSORS_ADM1266 + tristate "Analog Devices ADM1266 Sequencer" + select CRC8 + depends on GPIOLIB + help + If you say yes here you get hardware monitoring support for Analog + Devices ADM1266 Cascadable Super Sequencer. + + This driver can also be built as a module. If so, the module will + be called adm1266. + config SENSORS_ADM1275 tristate "Analog Devices ADM1275 and compatibles" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index c4b15db996ad..da41d22be1c9 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_PMBUS) += pmbus_core.o obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o +obj-$(CONFIG_SENSORS_ADM1266) += adm1266.o obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o obj-$(CONFIG_SENSORS_BEL_PFE) += bel-pfe.o obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c new file mode 100644 index 000000000000..1d83c4f42eaf --- /dev/null +++ b/drivers/hwmon/pmbus/adm1266.c @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ADM1266 - Cascadable Super Sequencer with Margin + * Control and Fault Recording + * + * Copyright 2020 Analog Devices Inc. + */ + +#include <linux/bitfield.h> +#include <linux/crc8.h> +#include <linux/debugfs.h> +#include <linux/gpio/driver.h> +#include <linux/i2c.h> +#include <linux/i2c-smbus.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/nvmem-consumer.h> +#include <linux/nvmem-provider.h> +#include "pmbus.h" +#include <linux/slab.h> +#include <linux/timekeeping.h> + +#define ADM1266_BLACKBOX_CONFIG 0xD3 +#define ADM1266_PDIO_CONFIG 0xD4 +#define ADM1266_READ_STATE 0xD9 +#define ADM1266_READ_BLACKBOX 0xDE +#define ADM1266_SET_RTC 0xDF +#define ADM1266_GPIO_CONFIG 0xE1 +#define ADM1266_BLACKBOX_INFO 0xE6 +#define ADM1266_PDIO_STATUS 0xE9 +#define ADM1266_GPIO_STATUS 0xEA + +/* ADM1266 GPIO defines */ +#define ADM1266_GPIO_NR 9 +#define ADM1266_GPIO_FUNCTIONS(x) FIELD_GET(BIT(0), x) +#define ADM1266_GPIO_INPUT_EN(x) FIELD_GET(BIT(2), x) +#define ADM1266_GPIO_OUTPUT_EN(x) FIELD_GET(BIT(3), x) +#define ADM1266_GPIO_OPEN_DRAIN(x) FIELD_GET(BIT(4), x) + +/* ADM1266 PDIO defines */ +#define ADM1266_PDIO_NR 16 +#define ADM1266_PDIO_PIN_CFG(x) FIELD_GET(GENMASK(15, 13), x) +#define ADM1266_PDIO_GLITCH_FILT(x) FIELD_GET(GENMASK(12, 9), x) +#define ADM1266_PDIO_OUT_CFG(x) FIELD_GET(GENMASK(2, 0), x) + +#define ADM1266_BLACKBOX_OFFSET 0 +#define ADM1266_BLACKBOX_SIZE 64 + +#define ADM1266_PMBUS_BLOCK_MAX 255 + +struct adm1266_data { + struct pmbus_driver_info info; + struct gpio_chip gc; + const char *gpio_names[ADM1266_GPIO_NR + ADM1266_PDIO_NR]; + struct i2c_client *client; + struct dentry *debugfs_dir; + struct nvmem_config nvmem_config; + struct nvmem_device *nvmem; + u8 *dev_mem; + struct mutex buf_mutex; + u8 write_buf[ADM1266_PMBUS_BLOCK_MAX + 1] ____cacheline_aligned; + u8 read_buf[ADM1266_PMBUS_BLOCK_MAX + 1] ____cacheline_aligned; +}; + +static const struct nvmem_cell_info adm1266_nvmem_cells[] = { + { + .name = "blackbox", + .offset = ADM1266_BLACKBOX_OFFSET, + .bytes = 2048, + }, +}; + +DECLARE_CRC8_TABLE(pmbus_crc_table); + +/* + * Different from Block Read as it sends data and waits for the slave to + * return a value dependent on that data. The protocol is simply a Write Block + * followed by a Read Block without the Read-Block command field and the + * Write-Block STOP bit. + */ +static int adm1266_pmbus_block_xfer(struct adm1266_data *data, u8 cmd, u8 w_len, u8 *data_w, + u8 *data_r) +{ + struct i2c_client *client = data->client; + struct i2c_msg msgs[2] = { + { + .addr = client->addr, + .flags = I2C_M_DMA_SAFE, + .buf = data->write_buf, + .len = w_len + 2, + }, + { + .addr = client->addr, + .flags = I2C_M_RD | I2C_M_DMA_SAFE, + .buf = data->read_buf, + .len = ADM1266_PMBUS_BLOCK_MAX + 2, + } + }; + u8 addr; + u8 crc; + int ret; + + mutex_lock(&data->buf_mutex); + + msgs[0].buf[0] = cmd; + msgs[0].buf[1] = w_len; + memcpy(&msgs[0].buf[2], data_w, w_len); + + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret != 2) { + if (ret >= 0) + ret = -EPROTO; + + mutex_unlock(&data->buf_mutex); + + return ret; + } + + if (client->flags & I2C_CLIENT_PEC) { + addr = i2c_8bit_addr_from_msg(&msgs[0]); + crc = crc8(pmbus_crc_table, &addr, 1, 0); + crc = crc8(pmbus_crc_table, msgs[0].buf, msgs[0].len, crc); + + addr = i2c_8bit_addr_from_msg(&msgs[1]); + crc = crc8(pmbus_crc_table, &addr, 1, crc); + crc = crc8(pmbus_crc_table, msgs[1].buf, msgs[1].buf[0] + 1, crc); + + if (crc != msgs[1].buf[msgs[1].buf[0] + 1]) { + mutex_unlock(&data->buf_mutex); + return -EBADMSG; + } + } + + memcpy(data_r, &msgs[1].buf[1], msgs[1].buf[0]); + + ret = msgs[1].buf[0]; + mutex_unlock(&data->buf_mutex); + + return ret; +} + +static const unsigned int adm1266_gpio_mapping[ADM1266_GPIO_NR][2] = { + {1, 0}, + {2, 1}, + {3, 2}, + {4, 8}, + {5, 9}, + {6, 10}, + {7, 11}, + {8, 6}, + {9, 7}, +}; + +static const char *adm1266_names[ADM1266_GPIO_NR + ADM1266_PDIO_NR] = { + "GPIO1", "GPIO2", "GPIO3", "GPIO4", "GPIO5", "GPIO6", "GPIO7", "GPIO8", + "GPIO9", "PDIO1", "PDIO2", "PDIO3", "PDIO4", "PDIO5", "PDIO6", + "PDIO7", "PDIO8", "PDIO9", "PDIO10", "PDIO11", "PDIO12", "PDIO13", + "PDIO14", "PDIO15", "PDIO16", +}; + +static int adm1266_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct adm1266_data *data = gpiochip_get_data(chip); + u8 read_buf[I2C_SMBUS_BLOCK_MAX + 1]; + unsigned long pins_status; + unsigned int pmbus_cmd; + int ret; + + if (offset < ADM1266_GPIO_NR) + pmbus_cmd = ADM1266_GPIO_STATUS; + else + pmbus_cmd = ADM1266_PDIO_STATUS; + + ret = i2c_smbus_read_block_data(data->client, pmbus_cmd, read_buf); + if (ret < 0) + return ret; + + pins_status = read_buf[0] + (read_buf[1] << 8); + if (offset < ADM1266_GPIO_NR) + return test_bit(adm1266_gpio_mapping[offset][1], &pins_status); + + return test_bit(offset - ADM1266_GPIO_NR, &pins_status); +} + +static int adm1266_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask, + unsigned long *bits) +{ + struct adm1266_data *data = gpiochip_get_data(chip); + u8 read_buf[ADM1266_PMBUS_BLOCK_MAX + 1]; + unsigned long status; + unsigned int gpio_nr; + int ret; + + ret = i2c_smbus_read_block_data(data->client, ADM1266_GPIO_STATUS, read_buf); + if (ret < 0) + return ret; + + status = read_buf[0] + (read_buf[1] << 8); + + *bits = 0; + for_each_set_bit(gpio_nr, mask, ADM1266_GPIO_NR) { + if (test_bit(adm1266_gpio_mapping[gpio_nr][1], &status)) + set_bit(gpio_nr, bits); + } + + ret = i2c_smbus_read_block_data(data->client, ADM1266_PDIO_STATUS, read_buf); + if (ret < 0) + return ret; + + status = read_buf[0] + (read_buf[1] << 8); + + *bits = 0; + for_each_set_bit_from(gpio_nr, mask, ADM1266_GPIO_NR + ADM1266_PDIO_STATUS) { + if (test_bit(gpio_nr - ADM1266_GPIO_NR, &status)) + set_bit(gpio_nr, bits); + } + + return 0; +} + +static void adm1266_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + struct adm1266_data *data = gpiochip_get_data(chip); + u8 read_buf[ADM1266_PMBUS_BLOCK_MAX + 1]; + unsigned long gpio_config; + unsigned long pdio_config; + unsigned long pin_cfg; + u8 write_cmd; + int ret; + int i; + + for (i = 0; i < ADM1266_GPIO_NR; i++) { + write_cmd = adm1266_gpio_mapping[i][1]; + ret = adm1266_pmbus_block_xfer(data, ADM1266_GPIO_CONFIG, 1, &write_cmd, read_buf); + if (ret != 2) + return; + + gpio_config = read_buf[0]; + seq_puts(s, adm1266_names[i]); + + seq_puts(s, " ( "); + if (!ADM1266_GPIO_FUNCTIONS(gpio_config)) { + seq_puts(s, "high-Z )\n"); + continue; + } + if (ADM1266_GPIO_INPUT_EN(gpio_config)) + seq_puts(s, "input "); + if (ADM1266_GPIO_OUTPUT_EN(gpio_config)) + seq_puts(s, "output "); + if (ADM1266_GPIO_OPEN_DRAIN(gpio_config)) + seq_puts(s, "open-drain )\n"); + else + seq_puts(s, "push-pull )\n"); + } + + write_cmd = 0xFF; + ret = adm1266_pmbus_block_xfer(data, ADM1266_PDIO_CONFIG, 1, &write_cmd, read_buf); + if (ret != 32) + return; + + for (i = 0; i < ADM1266_PDIO_NR; i++) { + seq_puts(s, adm1266_names[ADM1266_GPIO_NR + i]); + + pdio_config = read_buf[2 * i]; + pdio_config += (read_buf[2 * i + 1] << 8); + pin_cfg = ADM1266_PDIO_PIN_CFG(pdio_config); + + seq_puts(s, " ( "); + if (!pin_cfg || pin_cfg > 5) { + seq_puts(s, "high-Z )\n"); + continue; + } + + if (pin_cfg & BIT(0)) + seq_puts(s, "output "); + + if (pin_cfg & BIT(1)) + seq_puts(s, "input "); + + seq_puts(s, ")\n"); + } +} + +static int adm1266_config_gpio(struct adm1266_data *data) +{ + const char *name = dev_name(&data->client->dev); + char *gpio_name; + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(data->gpio_names); i++) { + gpio_name = devm_kasprintf(&data->client->dev, GFP_KERNEL, "adm1266-%x-%s", + data->client->addr, adm1266_names[i]); + if (!gpio_name) + return -ENOMEM; + + data->gpio_names[i] = gpio_name; + } + + data->gc.label = name; + data->gc.parent = &data->client->dev; + data->gc.owner = THIS_MODULE; + data->gc.base = -1; + data->gc.names = data->gpio_names; + data->gc.ngpio = ARRAY_SIZE(data->gpio_names); + data->gc.get = adm1266_gpio_get; + data->gc.get_multiple = adm1266_gpio_get_multiple; + data->gc.dbg_show = adm1266_gpio_dbg_show; + + ret = devm_gpiochip_add_data(&data->client->dev, &data->gc, data); + if (ret) + dev_err(&data->client->dev, "GPIO registering failed (%d)\n", ret); + + return ret; +} + +static int adm1266_state_read(struct seq_file *s, void *pdata) +{ + struct device *dev = s->private; + struct i2c_client *client = to_i2c_client(dev); + int ret; + + ret = i2c_smbus_read_word_data(client, ADM1266_READ_STATE); + if (ret < 0) + return ret; + + seq_printf(s, "%d\n", ret); + + return 0; +} + +static void adm1266_init_debugfs(struct adm1266_data *data) +{ + struct dentry *root; + + root = pmbus_get_debugfs_dir(data->client); + if (!root) + return; + + data->debugfs_dir = debugfs_create_dir(data->client->name, root); + if (!data->debugfs_dir) + return; + + debugfs_create_devm_seqfile(&data->client->dev, "sequencer_state", data->debugfs_dir, + adm1266_state_read); +} + +static int adm1266_nvmem_read_blackbox(struct adm1266_data *data, u8 *read_buff) +{ + int record_count; + char index; + u8 buf[5]; + int ret; + + ret = i2c_smbus_read_block_data(data->client, ADM1266_BLACKBOX_INFO, buf); + if (ret < 0) + return ret; + + if (ret != 4) + return -EIO; + + record_count = buf[3]; + + for (index = 0; index < record_count; index++) { + ret = adm1266_pmbus_block_xfer(data, ADM1266_READ_BLACKBOX, 1, &index, read_buff); + if (ret < 0) + return ret; + + if (ret != ADM1266_BLACKBOX_SIZE) + return -EIO; + + read_buff += ADM1266_BLACKBOX_SIZE; + } + + return 0; +} + +static int adm1266_nvmem_read(void *priv, unsigned int offset, void *val, size_t bytes) +{ + struct adm1266_data *data = priv; + int ret; + + if (offset + bytes > data->nvmem_config.size) + return -EINVAL; + + if (offset == 0) { + memset(data->dev_mem, 0, data->nvmem_config.size); + + ret = adm1266_nvmem_read_blackbox(data, data->dev_mem); + if (ret) { + dev_err(&data->client->dev, "Could not read blackbox!"); + return ret; + } + } + + memcpy(val, data->dev_mem + offset, bytes); + + return 0; +} + +static int adm1266_config_nvmem(struct adm1266_data *data) +{ + data->nvmem_config.name = dev_name(&data->client->dev); + data->nvmem_config.dev = &data->client->dev; + data->nvmem_config.root_only = true; + data->nvmem_config.read_only = true; + data->nvmem_config.owner = THIS_MODULE; + data->nvmem_config.reg_read = adm1266_nvmem_read; + data->nvmem_config.cells = adm1266_nvmem_cells; + data->nvmem_config.ncells = ARRAY_SIZE(adm1266_nvmem_cells); + data->nvmem_config.priv = data; + data->nvmem_config.stride = 1; + data->nvmem_config.word_size = 1; + data->nvmem_config.size = adm1266_nvmem_cells[0].bytes; + + data->dev_mem = devm_kzalloc(&data->client->dev, data->nvmem_config.size, GFP_KERNEL); + if (!data->dev_mem) + return -ENOMEM; + + data->nvmem = devm_nvmem_register(&data->client->dev, &data->nvmem_config); + if (IS_ERR(data->nvmem)) { + dev_err(&data->client->dev, "Could not register nvmem!"); + return PTR_ERR(data->nvmem); + } + + return 0; +} + +static int adm1266_set_rtc(struct adm1266_data *data) +{ + time64_t kt; + char write_buf[6]; + int i; + + kt = ktime_get_seconds(); + + memset(write_buf, 0, sizeof(write_buf)); + + for (i = 0; i < 4; i++) + write_buf[2 + i] = (kt >> (i * 8)) & 0xFF; + + return i2c_smbus_write_block_data(data->client, ADM1266_SET_RTC, sizeof(write_buf), + write_buf); +} + +static int adm1266_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct adm1266_data *data; + int ret; + int i; + + data = devm_kzalloc(&client->dev, sizeof(struct adm1266_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + data->info.pages = 17; + data->info.format[PSC_VOLTAGE_OUT] = linear; + for (i = 0; i < data->info.pages; i++) + data->info.func[i] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + + crc8_populate_msb(pmbus_crc_table, 0x7); + mutex_init(&data->buf_mutex); + + ret = adm1266_config_gpio(data); + if (ret < 0) + return ret; + + ret = adm1266_set_rtc(data); + if (ret < 0) + return ret; + + ret = adm1266_config_nvmem(data); + if (ret < 0) + return ret; + + ret = pmbus_do_probe(client, id, &data->info); + if (ret) + return ret; + + adm1266_init_debugfs(data); + + return 0; +} + +static const struct of_device_id adm1266_of_match[] = { + { .compatible = "adi,adm1266" }, + { } +}; +MODULE_DEVICE_TABLE(of, adm1266_of_match); + +static const struct i2c_device_id adm1266_id[] = { + { "adm1266", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adm1266_id); + +static struct i2c_driver adm1266_driver = { + .driver = { + .name = "adm1266", + .of_match_table = adm1266_of_match, + }, + .probe = adm1266_probe, + .remove = pmbus_do_remove, + .id_table = adm1266_id, +}; + +module_i2c_driver(adm1266_driver); + +MODULE_AUTHOR("Alexandru Tachici <alexandru.tachici@analog.com>"); +MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1266"); +MODULE_LICENSE("GPL v2");