123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- * Hardware monitoring driver for PIM4006, PIM4328 and PIM4820
- *
- * Copyright (c) 2021 Flextronics International Sweden AB
- */
- #include <linux/err.h>
- #include <linux/i2c.h>
- #include <linux/init.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/pmbus.h>
- #include <linux/slab.h>
- #include "pmbus.h"
- enum chips { pim4006, pim4328, pim4820 };
- struct pim4328_data {
- enum chips id;
- struct pmbus_driver_info info;
- };
- #define to_pim4328_data(x) container_of(x, struct pim4328_data, info)
- /* PIM4006 and PIM4328 */
- #define PIM4328_MFR_READ_VINA 0xd3
- #define PIM4328_MFR_READ_VINB 0xd4
- /* PIM4006 */
- #define PIM4328_MFR_READ_IINA 0xd6
- #define PIM4328_MFR_READ_IINB 0xd7
- #define PIM4328_MFR_FET_CHECKSTATUS 0xd9
- /* PIM4328 */
- #define PIM4328_MFR_STATUS_BITS 0xd5
- /* PIM4820 */
- #define PIM4328_MFR_READ_STATUS 0xd0
- static const struct i2c_device_id pim4328_id[] = {
- {"bmr455", pim4328},
- {"pim4006", pim4006},
- {"pim4106", pim4006},
- {"pim4206", pim4006},
- {"pim4306", pim4006},
- {"pim4328", pim4328},
- {"pim4406", pim4006},
- {"pim4820", pim4820},
- {}
- };
- MODULE_DEVICE_TABLE(i2c, pim4328_id);
- static int pim4328_read_word_data(struct i2c_client *client, int page,
- int phase, int reg)
- {
- int ret;
- if (page > 0)
- return -ENXIO;
- if (phase == 0xff)
- return -ENODATA;
- switch (reg) {
- case PMBUS_READ_VIN:
- ret = pmbus_read_word_data(client, page, phase,
- phase == 0 ? PIM4328_MFR_READ_VINA
- : PIM4328_MFR_READ_VINB);
- break;
- case PMBUS_READ_IIN:
- ret = pmbus_read_word_data(client, page, phase,
- phase == 0 ? PIM4328_MFR_READ_IINA
- : PIM4328_MFR_READ_IINB);
- break;
- default:
- ret = -ENODATA;
- }
- return ret;
- }
- static int pim4328_read_byte_data(struct i2c_client *client, int page, int reg)
- {
- const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
- struct pim4328_data *data = to_pim4328_data(info);
- int ret, status;
- if (page > 0)
- return -ENXIO;
- switch (reg) {
- case PMBUS_STATUS_BYTE:
- ret = pmbus_read_byte_data(client, page, PMBUS_STATUS_BYTE);
- if (ret < 0)
- return ret;
- if (data->id == pim4006) {
- status = pmbus_read_word_data(client, page, 0xff,
- PIM4328_MFR_FET_CHECKSTATUS);
- if (status < 0)
- return status;
- if (status & 0x0630) /* Input UV */
- ret |= PB_STATUS_VIN_UV;
- } else if (data->id == pim4328) {
- status = pmbus_read_byte_data(client, page,
- PIM4328_MFR_STATUS_BITS);
- if (status < 0)
- return status;
- if (status & 0x04) /* Input UV */
- ret |= PB_STATUS_VIN_UV;
- if (status & 0x40) /* Output UV */
- ret |= PB_STATUS_NONE_ABOVE;
- } else if (data->id == pim4820) {
- status = pmbus_read_byte_data(client, page,
- PIM4328_MFR_READ_STATUS);
- if (status < 0)
- return status;
- if (status & 0x05) /* Input OV or OC */
- ret |= PB_STATUS_NONE_ABOVE;
- if (status & 0x1a) /* Input UV */
- ret |= PB_STATUS_VIN_UV;
- if (status & 0x40) /* OT */
- ret |= PB_STATUS_TEMPERATURE;
- }
- break;
- default:
- ret = -ENODATA;
- }
- return ret;
- }
- static int pim4328_probe(struct i2c_client *client)
- {
- int status;
- u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];
- const struct i2c_device_id *mid;
- struct pim4328_data *data;
- struct pmbus_driver_info *info;
- struct pmbus_platform_data *pdata;
- struct device *dev = &client->dev;
- if (!i2c_check_functionality(client->adapter,
- I2C_FUNC_SMBUS_READ_BYTE_DATA
- | I2C_FUNC_SMBUS_BLOCK_DATA))
- return -ENODEV;
- data = devm_kzalloc(&client->dev, sizeof(struct pim4328_data),
- GFP_KERNEL);
- if (!data)
- return -ENOMEM;
- status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id);
- if (status < 0) {
- dev_err(&client->dev, "Failed to read Manufacturer Model\n");
- return status;
- }
- for (mid = pim4328_id; mid->name[0]; mid++) {
- if (!strncasecmp(mid->name, device_id, strlen(mid->name)))
- break;
- }
- if (!mid->name[0]) {
- dev_err(&client->dev, "Unsupported device\n");
- return -ENODEV;
- }
- if (strcmp(client->name, mid->name))
- dev_notice(&client->dev,
- "Device mismatch: Configured %s, detected %s\n",
- client->name, mid->name);
- data->id = mid->driver_data;
- info = &data->info;
- info->pages = 1;
- info->read_byte_data = pim4328_read_byte_data;
- info->read_word_data = pim4328_read_word_data;
- pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data),
- GFP_KERNEL);
- if (!pdata)
- return -ENOMEM;
- dev->platform_data = pdata;
- pdata->flags = PMBUS_NO_CAPABILITY | PMBUS_NO_WRITE_PROTECT;
- switch (data->id) {
- case pim4006:
- info->phases[0] = 2;
- info->func[0] = PMBUS_PHASE_VIRTUAL | PMBUS_HAVE_VIN
- | PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT;
- info->pfunc[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN;
- info->pfunc[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN;
- break;
- case pim4328:
- info->phases[0] = 2;
- info->func[0] = PMBUS_PHASE_VIRTUAL
- | PMBUS_HAVE_VCAP | PMBUS_HAVE_VIN
- | PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT;
- info->pfunc[0] = PMBUS_HAVE_VIN;
- info->pfunc[1] = PMBUS_HAVE_VIN;
- info->format[PSC_VOLTAGE_IN] = direct;
- info->format[PSC_TEMPERATURE] = direct;
- info->format[PSC_CURRENT_OUT] = direct;
- pdata->flags |= PMBUS_USE_COEFFICIENTS_CMD;
- break;
- case pim4820:
- info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_TEMP
- | PMBUS_HAVE_IIN;
- info->format[PSC_VOLTAGE_IN] = direct;
- info->format[PSC_TEMPERATURE] = direct;
- info->format[PSC_CURRENT_IN] = direct;
- pdata->flags |= PMBUS_USE_COEFFICIENTS_CMD;
- break;
- default:
- return -ENODEV;
- }
- return pmbus_do_probe(client, info);
- }
- static struct i2c_driver pim4328_driver = {
- .driver = {
- .name = "pim4328",
- },
- .probe_new = pim4328_probe,
- .id_table = pim4328_id,
- };
- module_i2c_driver(pim4328_driver);
- MODULE_AUTHOR("Erik Rosen <[email protected]>");
- MODULE_DESCRIPTION("PMBus driver for PIM4006, PIM4328, PIM4820 power interface modules");
- MODULE_LICENSE("GPL");
- MODULE_IMPORT_NS(PMBUS);
|