[PATCH] char: add driver for mps VR controller mp2891

From: Noah (Wensheng) Wang
Date: Tue Mar 14 2023 - 05:40:42 EST


Hi Arnd, Grey:
Thanks for the review.

This driver will be used by facebook. This driver provide a device node for userspace to get output voltage, input voltage, input current, input power, output power and temperature of mp2891 controller through I2C. This driver determine what kind of value the userspace wants through the mp2891_write interface and return the corresponding value when the interface mp2891_read is called.

Signed-off-by: Noah Wang <Noah.Wang@xxxxxxxxxxxxxxxxxxx>
---
drivers/char/mp2891.c | 403 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 403 insertions(+)
create mode 100644 drivers/char/mp2891.c

diff --git a/drivers/char/mp2891.c b/drivers/char/mp2891.c new file mode 100644 index 000000000000..84529b73f065
--- /dev/null
+++ b/drivers/char/mp2891.c
@@ -0,0 +1,403 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for MPS Multi-phase Digital VR Controllers(MP2891)
+ *
+ * Copyright (C) 2023 MPS
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/ide.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/gpio.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/of_gpio.h>
+#include <linux/semaphore.h>
+#include <linux/timer.h>
+#include <linux/i2c.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <asm/mach/map.h>
+
+#define PMBUS_PAGE 0x00
+#define MFR_VOUT_LOOP_CTRL_R1 0xBD
+#define MFR_VOUT_LOOP_CTRL_R2 0xBD
+
+#define VID_STEP_POS 14
+#define VID_STEP_MSK (0x3 << VID_STEP_POS)
+
+#define READ_VIN 0x88
+#define READ_VOUT 0x8B
+#define READ_IOUT 0x8C
+#define READ_TEMPERATURE 0x8D
+#define READ_PIN_EST_PMBUS_R1 0x94
+#define READ_PIN_EST_PMBUS_R2 0x94
+#define READ_POUT_PMBUS_R1 0x96
+#define READ_POUT_PMBUS_R2 0x96
+
+#define MP2891_PAGE_NUM 2
+
+#define MP2891_CNT 1
+#define MP2891_NAME "mp2891"
+
+#define IOUT_PAGE0 "IOUT-0"
+#define IOUT_PAGE1 "IOUT-1"
+#define VOUT_PAGE0 "VOUT-0"
+#define VOUT_PAGE1 "VOUT-1"
+#define TEMPERATURE_PAGE0 "TEMPERATURE-0"
+#define TEMPERATURE_PAGE1 "TEMPERATURE-1"
+#define VIN_PAGE0 "VIN-0"
+#define PIN_EST_PAGE0 "PIN_EST-0"
+#define PIN_EST_PAGE1 "PIN_EST-1"
+#define POUT_PAGE0 "POUT-0"
+#define POUT_PAGE1 "POUT-1"
+
+struct mp2891_data {
+ int vid_step[MP2891_PAGE_NUM];
+};
+
+struct mp2891_dev {
+ dev_t devid;
+ struct cdev cdev;
+ struct class *class;
+ struct device *device;
+ int major;
+ char read_flag[20];
+ struct i2c_client *client;
+ struct mp2891_data *data;
+};
+
+struct mp2891_dev mp2891cdev;
+
+static int read_word_data(struct i2c_client *client, int page, int reg)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ if (ret < 0)
+ return ret;
+ ret = i2c_smbus_read_word_data(client, reg);
+
+ return ret;
+}
+
+static int
+mp2891_read_pout(struct i2c_client *client, struct mp2891_data *data,
+ int page, int reg)
+{
+ int ret;
+
+ ret = read_word_data(client, page, reg);
+ if ((ret & 0x8000) == 0)
+ ret = (ret & 0x7FF) * (((ret & 0x7800) >> 11) + 1);
+ else
+ ret = (ret & 0x7FF) >> (32 - ((ret & 0xF800) >> 11));
+
+ return ret;
+}
+
+static int
+mp2891_read_pin(struct i2c_client *client, struct mp2891_data *data,
+ int page, int reg)
+{
+ int ret;
+
+ ret = read_word_data(client, page, reg);
+ if ((ret & 0x8000) == 0)
+ ret = (ret & 0x7FF) * (((ret & 0x7800) >> 11) + 1);
+ else
+ ret = (ret & 0x7FF) >> (32 - ((ret & 0xF800) >> 11));
+
+ return ret;
+}
+
+static int
+mp2891_read_vin(struct i2c_client *client, struct mp2891_data *data,
+ int page, int reg)
+{
+ int ret;
+
+ ret = read_word_data(client, page, reg);
+ ret = ((ret & 0x7FF) * 1000) >> 5;
+
+ return ret;
+}
+
+static int
+mp2891_read_vout(struct i2c_client *client, struct mp2891_data *data,
+ int page, int reg)
+{
+ int ret;
+
+ ret = read_word_data(client, page, reg);
+ if (data->vid_step[page] == 10)
+ ret = ((ret & 0xFFF) * data->vid_step[page]) >> 2;
+ else
+ ret = (ret & 0xFFF) * data->vid_step[page];
+
+ return ret;
+}
+
+static int
+mp2891_read_iout(struct i2c_client *client, struct mp2891_data *data,
+ int page, int reg)
+{
+ int ret;
+
+ ret = read_word_data(client, page, reg);
+ if (((ret & 0x8000) >> 15) == 0)
+ ret = ((ret & 0x7FF) * 1000) * (((ret & 0x7800) >> 11) + 1);
+ else
+ ret = ((ret & 0x7FF) * 1000) >> (32 - ((ret & 0xF800) >> 11));
+
+ return ret;
+}
+
+static int
+mp2891_read_temperature(struct i2c_client *client, struct mp2891_data *data,
+ int page, int reg)
+{
+ int ret;
+
+ ret = read_word_data(client, page, reg);
+ if (((ret & 0x400) >> 10) == 0)
+ ret = ret & 0x7FF;
+ else
+ ret = ~(ret & 0x7FF) + 1;
+
+ return ret;
+}
+
+static int mp2891_open(struct inode *node, struct file *filp) {
+ filp->private_data = &mp2891cdev;
+ return 0;
+}
+
+static ssize_t mp2891_write(struct file *filp, const char __user *buf,
+size_t cnt, loff_t *offt) {
+ int ret;
+
+ ret = copy_from_user(mp2891cdev.read_flag, buf, cnt);
+ if (ret < 0)
+ return -EFAULT;
+
+ return ret;
+}
+
+static int mp2891_read(struct file *filp, char __user *buf, size_t cnt,
+loff_t *off) {
+ int ret;
+ unsigned int i;
+ long err;
+ unsigned char data[10];
+ char *ptr = NULL;
+ struct mp2891_dev *dev = NULL;
+
+ dev = (struct mp2891_dev *)filp->private_data;
+ if (strncmp(dev->read_flag, IOUT_PAGE0, strlen(IOUT_PAGE0)) == 0) {
+ ret = mp2891_read_iout(dev->client, dev->data, 0, READ_IOUT);
+ } else if (strncmp(dev->read_flag, IOUT_PAGE1, strlen(IOUT_PAGE1)) == 0) {
+ ret = mp2891_read_iout(dev->client, dev->data, 1, READ_IOUT);
+ } else if (strncmp(dev->read_flag, VOUT_PAGE0, strlen(VOUT_PAGE0)) == 0) {
+ ret = mp2891_read_vout(dev->client, dev->data, 0, READ_VOUT);
+ } else if (strncmp(dev->read_flag, VOUT_PAGE1, strlen(VOUT_PAGE1)) == 0) {
+ ret = mp2891_read_vout(dev->client, dev->data, 1, READ_VOUT);
+ } else if (strncmp(dev->read_flag, TEMPERATURE_PAGE0,
+ strlen(TEMPERATURE_PAGE0)) == 0) {
+ ret = mp2891_read_temperature(dev->client, dev->data, 0, READ_TEMPERATURE);
+ } else if (strncmp(dev->read_flag, TEMPERATURE_PAGE1,
+ strlen(TEMPERATURE_PAGE1)) == 0) {
+ ret = mp2891_read_temperature(dev->client, dev->data, 1, READ_TEMPERATURE);
+ } else if (strncmp(dev->read_flag, VIN_PAGE0, strlen(VIN_PAGE0)) == 0) {
+ ret = mp2891_read_vin(dev->client, dev->data, 0, READ_VIN);
+ } else if (strncmp(dev->read_flag, PIN_EST_PAGE0, strlen(PIN_EST_PAGE0)) == 0) {
+ ret = mp2891_read_pin(dev->client, dev->data, 0, READ_PIN_EST_PMBUS_R1);
+ } else if (strncmp(dev->read_flag, PIN_EST_PAGE1, strlen(PIN_EST_PAGE1)) == 0) {
+ ret = mp2891_read_pin(dev->client, dev->data, 1, READ_PIN_EST_PMBUS_R2);
+ ret = 5;
+ } else if (strncmp(dev->read_flag, POUT_PAGE0, strlen(POUT_PAGE0)) == 0) {
+ ret = mp2891_read_pout(dev->client, dev->data, 0, READ_POUT_PMBUS_R1);
+ } else if (strncmp(dev->read_flag, POUT_PAGE1, strlen(POUT_PAGE1)) == 0) {
+ ret = mp2891_read_pout(dev->client, dev->data, 1, READ_POUT_PMBUS_R2);
+ } else {
+ ret = 0;
+ }
+ if (ret < 0)
+ return ret;
+
+ ptr = (char *)&ret;
+ for (i = 0; i < sizeof(int); i++)
+ data[i] = ptr[i];
+
+ err = copy_to_user(buf, data, sizeof(data));
+
+ return 0;
+}
+
+static int mp2891_release(struct inode *inode, struct file *filp) {
+ return 0;
+}
+
+static const struct file_operations mp2891cdev_fops = {
+ .owner = THIS_MODULE,
+ .open = mp2891_open,
+ .write = mp2891_write,
+ .read = mp2891_read,
+ .release = mp2891_release,
+};
+
+static int
+mp2891_identify_vid(struct i2c_client *client, struct mp2891_data *data,
+ u32 reg, int page)
+{
+ int ret;
+
+ ret = read_word_data(client, page, reg);
+
+ if (ret < 0)
+ return ret;
+
+ if (((ret & 0x2000) >> 13) == 1) {
+ data->vid_step[page] = 10;
+ return 0;
+ }
+
+ /* 01b - 5mV, 10b - 2.5mV, else - 2mV */
+ ret = ((ret & VID_STEP_MSK) >> VID_STEP_POS);
+ if (ret == 1)
+ data->vid_step[page] = 5;
+ else if (ret == 2)
+ data->vid_step[page] = 2;
+ else
+ data->vid_step[page] = 0;
+
+ return 0;
+}
+
+static int
+mp2891_identify_rails_vid(struct i2c_client *client, struct mp2891_data
+*data) {
+ int ret;
+
+ /* Identify vid_step for rail 1. */
+ ret = mp2891_identify_vid(client, data, MFR_VOUT_LOOP_CTRL_R1, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Identify vid_step for rail 2 */
+ ret = mp2891_identify_vid(client, data, MFR_VOUT_LOOP_CTRL_R2, 1);
+ return ret;
+}
+
+static int mp2891_create_device_node(struct i2c_client *client) {
+ if (mp2891cdev.major) {
+ mp2891cdev.devid = MKDEV(mp2891cdev.major, 0);
+ register_chrdev_region(mp2891cdev.devid, MP2891_CNT, MP2891_NAME);
+ } else {
+ alloc_chrdev_region(&mp2891cdev.devid, 0, MP2891_CNT, MP2891_NAME);
+ mp2891cdev.major = MAJOR(mp2891cdev.devid);
+ }
+
+ cdev_init(&mp2891cdev.cdev, &mp2891cdev_fops);
+ cdev_add(&mp2891cdev.cdev, mp2891cdev.devid, MP2891_CNT);
+
+ mp2891cdev.class = class_create(THIS_MODULE, MP2891_NAME);
+ if (IS_ERR(mp2891cdev.class))
+ return PTR_ERR(mp2891cdev.class);
+
+ mp2891cdev.device = device_create(mp2891cdev.class, NULL, mp2891cdev.devid, NULL, MP2891_NAME);
+ if (IS_ERR(mp2891cdev.device))
+ return PTR_ERR(mp2891cdev.device);
+
+ mp2891cdev.client = client;
+
+ return 0;
+}
+
+static int get_mp2891_data(struct i2c_client *client) {
+ int ret;
+
+ /* Identify VID setting per rail. */
+ ret = mp2891_identify_rails_vid(client, mp2891cdev.data);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int mp2891_probe(struct i2c_client *client, const struct
+i2c_device_id *id) {
+ int ret;
+
+ mp2891cdev.data = devm_kzalloc(&client->dev, sizeof(struct mp2891_data),
+ GFP_KERNEL);
+
+ ret = mp2891_create_device_node(client);
+ if (ret < 0)
+ return ret;
+
+ ret = get_mp2891_data(client);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int mp2891_remove(struct i2c_client *client) {
+ cdev_del(&mp2891cdev.cdev);
+ unregister_chrdev_region(mp2891cdev.devid, MP2891_CNT);
+
+ device_destroy(mp2891cdev.class, mp2891cdev.devid);
+ class_destroy(mp2891cdev.class);
+ return 0;
+}
+
+static const struct i2c_device_id mp2891_id[] = {
+ {"mps,mp2891", 0},
+ {}
+};
+
+static const struct of_device_id mp2891_of_match[] = {
+ {.compatible = "mps,mp2891"},
+ {}
+};
+
+static struct i2c_driver mp2891_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "mp2891",
+ .of_match_table = mp2891_of_match,
+ },
+ .probe = mp2891_probe,
+ .remove = mp2891_remove,
+ .id_table = mp2891_id,
+};
+
+static int __init mp2891_init(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&mp2891_driver);
+ return ret;
+}
+
+static void __exit mp2891_exit(void)
+{
+ i2c_del_driver(&mp2891_driver);
+}
+
+module_init(mp2891_init);
+module_exit(mp2891_exit);
+
+MODULE_AUTHOR("Noah Wang <Noah.Wang@xxxxxxxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Monolithic Power Systems MP2891 voltage regulator
+driver"); MODULE_LICENSE("GPL");
--
2.25.1

Best regards
Noah Wang (王文圣)
Hangzhou MPS Semiconductor Technology Ltd.
A2-2-8F, Xixi Center, No.588 West Wenyi Road, Xihu District, Hangzhou, Zhejiang, China Tel: 86-571-89818734
E-mail: Noah.Wang@xxxxxxxxxxxxxxxxxxx