lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date:   Tue, 14 Mar 2023 09:18:50 +0000
From:   "Noah (Wensheng) Wang" <Noah.Wang@...olithicpower.com>
To:     "arnd@...db.de" <arnd@...db.de>,
        "gregkh@...uxfoundation.org" <gregkh@...uxfoundation.org>
CC:     "linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>,
        "Luke (Lijie) Jiang" <Luke.Jiang@...olithicpower.com>,
        pebble liang <pebble.liang@...olithicpower.com>,
        "Eva (Ting) Ma" <Eva.Ma@...olithicpower.com>
Subject: [PATCH] char: add driver for mps VR controller mp2891

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@...olithicpower.com>
---
 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@...olithicpower.com>");
+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@...olithicpower.com


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ