From nobody Wed Feb 11 14:20:47 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 88A25C6FD1C for ; Tue, 14 Mar 2023 09:40:42 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230421AbjCNJkj (ORCPT ); Tue, 14 Mar 2023 05:40:39 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:38554 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229538AbjCNJkf (ORCPT ); Tue, 14 Mar 2023 05:40:35 -0400 X-Greylist: delayed 1290 seconds by postgrey-1.37 at lindbergh.monkeyblade.net; Tue, 14 Mar 2023 02:40:28 PDT Received: from mx0a-002bf204.pphosted.com (mx0a-002bf204.pphosted.com [205.220.160.90]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9A1C15A1A2 for ; Tue, 14 Mar 2023 02:40:28 -0700 (PDT) Received: from pps.filterd (m0207524.ppops.net [127.0.0.1]) by mx0a-002bf204.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 32E6TKMm009655; Tue, 14 Mar 2023 02:18:55 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=monolithicpower.com; h=from : to : cc : subject : date : message-id : content-type : content-transfer-encoding : mime-version; s=pps1; bh=+JQits5Qco7Jmyn0lRYWSbG8Ycg73GCRoNNM1SwrDuE=; b=RFzWsHgugd5h2UmoJqdUnf0XIqx0tvEKjdFRO81RhmhZRatf1DC+YYgLGROCwsgiyemA tGR1qPXIp5JHwZbaHoJ1YxeYzuDMVeVdykd0NJ6RwA5C5nzm7MN55eecBUOALYwn6Ko7 G+2KH3QBjSMV/ZWP5aq1hfn7r/AZzLapaxpwF5cyAt1q8jdgVzO+TFLkeexQHZh3jWQq klQ3ltKCOtSjsFianplDeGETDKQ0ABA1hvgsTo/wbXFk2JBUVW4AtSK/zCx/4rOM9DJD X+VNx4rI6NgU8kolLBbH2XewMuvIdC7P8MU5v1N5WK2JI0kEqpg3aKA20FXaALeuF3wF Wg== Received: from webmail.monolithicpower.com (mps-vpn.monolithicpower.com [12.33.0.20] (may be forged)) by mx0a-002bf204.pphosted.com (PPS) with ESMTPS id 3p8nxwsdwu-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT); Tue, 14 Mar 2023 02:18:55 -0700 Received: from CD-MSH02.monolithicpower.com (10.10.70.19) by mps-mslbn02.monolithicpower.com (10.10.10.248) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.7; Tue, 14 Mar 2023 02:18:53 -0700 Received: from CD-MSH04.monolithicpower.com (10.10.70.213) by CD-MSH02.monolithicpower.com (10.10.70.19) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.7; Tue, 14 Mar 2023 17:18:50 +0800 Received: from CD-MSH04.monolithicpower.com ([fe80::fd9c:129e:f4d1:923f]) by CD-MSH04.monolithicpower.com ([fe80::fd9c:129e:f4d1:923f%4]) with mapi id 15.01.2242.004; Tue, 14 Mar 2023 17:18:50 +0800 From: "Noah (Wensheng) Wang" To: "arnd@arndb.de" , "gregkh@linuxfoundation.org" CC: "linux-kernel@vger.kernel.org" , "Luke (Lijie) Jiang" , pebble liang , "Eva (Ting) Ma" Subject: [PATCH] char: add driver for mps VR controller mp2891 Thread-Topic: [PATCH] char: add driver for mps VR controller mp2891 Thread-Index: AdlWVfx2NrxZQfJoQgSh26jdTLisZg== Date: Tue, 14 Mar 2023 09:18:50 +0000 Message-ID: <08e7bd6ed16f4bde95b674db940783ec@monolithicpower.com> Accept-Language: en-US Content-Language: zh-CN X-MS-Has-Attach: X-MS-TNEF-Correlator: x-originating-ip: [192.168.71.13] Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 X-Proofpoint-ORIG-GUID: zt7O1aigBsVhxOz_KXjRpM4_0iSoJsYU X-Proofpoint-GUID: zt7O1aigBsVhxOz_KXjRpM4_0iSoJsYU X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.942,Hydra:6.0.573,FMLib:17.11.170.22 definitions=2023-03-14_03,2023-03-14_01,2023-02-09_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 malwarescore=0 adultscore=0 priorityscore=1501 impostorscore=0 mlxlogscore=999 phishscore=0 mlxscore=0 spamscore=0 clxscore=1011 bulkscore=0 lowpriorityscore=0 suspectscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2212070000 definitions=main-2303140080 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 drive= r determine what kind of value the userspace wants through the mp2891_write= interface and return the corresponding value when the interface mp2891_rea= d is called. Signed-off-by: Noah Wang --- 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 10= 0644 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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)=20 +{ + int ret; + + ret =3D i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (ret < 0) + return ret; + ret =3D 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 =3D read_word_data(client, page, reg); + if ((ret & 0x8000) =3D=3D 0) + ret =3D (ret & 0x7FF) * (((ret & 0x7800) >> 11) + 1); + else + ret =3D (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 =3D read_word_data(client, page, reg); + if ((ret & 0x8000) =3D=3D 0) + ret =3D (ret & 0x7FF) * (((ret & 0x7800) >> 11) + 1); + else + ret =3D (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 =3D read_word_data(client, page, reg); + ret =3D ((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 =3D read_word_data(client, page, reg); + if (data->vid_step[page] =3D=3D 10) + ret =3D ((ret & 0xFFF) * data->vid_step[page]) >> 2; + else + ret =3D (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 =3D read_word_data(client, page, reg); + if (((ret & 0x8000) >> 15) =3D=3D 0) + ret =3D ((ret & 0x7FF) * 1000) * (((ret & 0x7800) >> 11) + 1); + else + ret =3D ((ret & 0x7FF) * 1000) >> (32 - ((ret & 0xF800) >> 11)); + + return ret; +} + +static int +mp2891_read_temperature(struct i2c_client *client, struct mp2891_data *dat= a, + int page, int reg) +{ + int ret; + + ret =3D read_word_data(client, page, reg); + if (((ret & 0x400) >> 10) =3D=3D 0) + ret =3D ret & 0x7FF; + else + ret =3D ~(ret & 0x7FF) + 1; + + return ret; +} + +static int mp2891_open(struct inode *node, struct file *filp) { + filp->private_data =3D &mp2891cdev; + return 0; +} + +static ssize_t mp2891_write(struct file *filp, const char __user *buf,=20 +size_t cnt, loff_t *offt) { + int ret; + + ret =3D 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,=20 +loff_t *off) { + int ret; + unsigned int i; + long err; + unsigned char data[10]; + char *ptr =3D NULL; + struct mp2891_dev *dev =3D NULL; + + dev =3D (struct mp2891_dev *)filp->private_data; + if (strncmp(dev->read_flag, IOUT_PAGE0, strlen(IOUT_PAGE0)) =3D=3D 0) { + ret =3D mp2891_read_iout(dev->client, dev->data, 0, READ_IOUT); + } else if (strncmp(dev->read_flag, IOUT_PAGE1, strlen(IOUT_PAGE1)) =3D=3D= 0) { + ret =3D mp2891_read_iout(dev->client, dev->data, 1, READ_IOUT); + } else if (strncmp(dev->read_flag, VOUT_PAGE0, strlen(VOUT_PAGE0)) =3D=3D= 0) { + ret =3D mp2891_read_vout(dev->client, dev->data, 0, READ_VOUT); + } else if (strncmp(dev->read_flag, VOUT_PAGE1, strlen(VOUT_PAGE1)) =3D=3D= 0) { + ret =3D mp2891_read_vout(dev->client, dev->data, 1, READ_VOUT); + } else if (strncmp(dev->read_flag, TEMPERATURE_PAGE0, + strlen(TEMPERATURE_PAGE0)) =3D=3D 0) { + ret =3D mp2891_read_temperature(dev->client, dev->data, 0, READ_TEMPERAT= URE); + } else if (strncmp(dev->read_flag, TEMPERATURE_PAGE1, + strlen(TEMPERATURE_PAGE1)) =3D=3D 0) { + ret =3D mp2891_read_temperature(dev->client, dev->data, 1, READ_TEMPERAT= URE); + } else if (strncmp(dev->read_flag, VIN_PAGE0, strlen(VIN_PAGE0)) =3D=3D 0= ) { + ret =3D mp2891_read_vin(dev->client, dev->data, 0, READ_VIN); + } else if (strncmp(dev->read_flag, PIN_EST_PAGE0, strlen(PIN_EST_PAGE0)) = =3D=3D 0) { + ret =3D 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)) = =3D=3D 0) { + ret =3D mp2891_read_pin(dev->client, dev->data, 1, READ_PIN_EST_PMBUS_R2= ); + ret =3D 5; + } else if (strncmp(dev->read_flag, POUT_PAGE0, strlen(POUT_PAGE0)) =3D=3D= 0) { + ret =3D mp2891_read_pout(dev->client, dev->data, 0, READ_POUT_PMBUS_R1); + } else if (strncmp(dev->read_flag, POUT_PAGE1, strlen(POUT_PAGE1)) =3D=3D= 0) { + ret =3D mp2891_read_pout(dev->client, dev->data, 1, READ_POUT_PMBUS_R2); + } else { + ret =3D 0; + } + if (ret < 0) + return ret; + + ptr =3D (char *)&ret; + for (i =3D 0; i < sizeof(int); i++) + data[i] =3D ptr[i]; + + err =3D 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 =3D { + .owner =3D THIS_MODULE, + .open =3D mp2891_open, + .write =3D mp2891_write, + .read =3D mp2891_read, + .release =3D mp2891_release, +}; + +static int +mp2891_identify_vid(struct i2c_client *client, struct mp2891_data *data, + u32 reg, int page) +{ + int ret; + + ret =3D read_word_data(client, page, reg); + + if (ret < 0) + return ret; + + if (((ret & 0x2000) >> 13) =3D=3D 1) { + data->vid_step[page] =3D 10; + return 0; + } + + /* 01b - 5mV, 10b - 2.5mV, else - 2mV */ + ret =3D ((ret & VID_STEP_MSK) >> VID_STEP_POS); + if (ret =3D=3D 1) + data->vid_step[page] =3D 5; + else if (ret =3D=3D 2) + data->vid_step[page] =3D 2; + else + data->vid_step[page] =3D 0; + + return 0; +} + +static int +mp2891_identify_rails_vid(struct i2c_client *client, struct mp2891_data=20 +*data) { + int ret; + + /* Identify vid_step for rail 1. */ + ret =3D mp2891_identify_vid(client, data, MFR_VOUT_LOOP_CTRL_R1, 0); + if (ret < 0) + return ret; + + /* Identify vid_step for rail 2 */ + ret =3D 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 =3D 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 =3D MAJOR(mp2891cdev.devid); + } + + cdev_init(&mp2891cdev.cdev, &mp2891cdev_fops); + cdev_add(&mp2891cdev.cdev, mp2891cdev.devid, MP2891_CNT); + + mp2891cdev.class =3D class_create(THIS_MODULE, MP2891_NAME); + if (IS_ERR(mp2891cdev.class)) + return PTR_ERR(mp2891cdev.class); + + mp2891cdev.device =3D device_create(mp2891cdev.class, NULL, mp2891cdev.de= vid, NULL, MP2891_NAME); + if (IS_ERR(mp2891cdev.device)) + return PTR_ERR(mp2891cdev.device); + + mp2891cdev.client =3D client; + + return 0; +} + +static int get_mp2891_data(struct i2c_client *client) { + int ret; + + /* Identify VID setting per rail. */ + ret =3D mp2891_identify_rails_vid(client, mp2891cdev.data); + if (ret < 0) + return ret; + + return 0; +} + +static int mp2891_probe(struct i2c_client *client, const struct=20 +i2c_device_id *id) { + int ret; + + mp2891cdev.data =3D devm_kzalloc(&client->dev, sizeof(struct mp2891_data), + GFP_KERNEL); + + ret =3D mp2891_create_device_node(client); + if (ret < 0) + return ret; + + ret =3D 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[] =3D { + {"mps,mp2891", 0}, + {} +}; + +static const struct of_device_id mp2891_of_match[] =3D { + {.compatible =3D "mps,mp2891"}, + {} +}; + +static struct i2c_driver mp2891_driver =3D { + .driver =3D { + .owner =3D THIS_MODULE, + .name =3D "mp2891", + .of_match_table =3D mp2891_of_match, + }, + .probe =3D mp2891_probe, + .remove =3D mp2891_remove, + .id_table =3D mp2891_id, +}; + +static int __init mp2891_init(void) +{ + int ret; + + ret =3D 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 "); +MODULE_DESCRIPTION("Monolithic Power Systems MP2891 voltage regulator=20 +driver"); MODULE_LICENSE("GPL"); -- 2.25.1 Best regards Noah=C2=A0Wang (=E7=8E=8B=E6=96=87=E5=9C=A3=EF=BC=89 Hangzhou MPS Semiconductor Technology Ltd. A2-2-8F, Xixi Center, No.588 West Wenyi Road, Xihu District, Hangzhou, Zhej= iang, China=C2=A0Tel: 86-571-89818734 E-mail:=C2=A0Noah.Wang@monolithicpower.com