]> Git Repo - J-linux.git/blob - drivers/hwmon/pmbus/tda38640.c
Merge tag 'vfs-6.13-rc7.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
[J-linux.git] / drivers / hwmon / pmbus / tda38640.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Hardware monitoring driver for Infineon TDA38640
4  *
5  * Copyright (c) 2023 9elements GmbH
6  *
7  */
8
9 #include <linux/err.h>
10 #include <linux/i2c.h>
11 #include <linux/init.h>
12 #include <linux/kernel.h>
13 #include <linux/module.h>
14 #include <linux/regulator/driver.h>
15 #include "pmbus.h"
16
17 static const struct regulator_desc __maybe_unused tda38640_reg_desc[] = {
18         PMBUS_REGULATOR_ONE("vout"),
19 };
20
21 struct tda38640_data {
22         struct pmbus_driver_info info;
23         u32 en_pin_lvl;
24 };
25
26 #define to_tda38640_data(x)  container_of(x, struct tda38640_data, info)
27
28 /*
29  * Map PB_ON_OFF_CONFIG_POLARITY_HIGH to PB_OPERATION_CONTROL_ON.
30  */
31 static int tda38640_read_byte_data(struct i2c_client *client, int page, int reg)
32 {
33         const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
34         struct tda38640_data *data = to_tda38640_data(info);
35         int ret, on_off_config, enabled;
36
37         if (reg != PMBUS_OPERATION)
38                 return -ENODATA;
39
40         ret = pmbus_read_byte_data(client, page, reg);
41         if (ret < 0)
42                 return ret;
43
44         on_off_config = pmbus_read_byte_data(client, page,
45                                              PMBUS_ON_OFF_CONFIG);
46         if (on_off_config < 0)
47                 return on_off_config;
48
49         enabled = !!(on_off_config & PB_ON_OFF_CONFIG_POLARITY_HIGH);
50
51         enabled ^= data->en_pin_lvl;
52         if (enabled)
53                 ret &= ~PB_OPERATION_CONTROL_ON;
54         else
55                 ret |= PB_OPERATION_CONTROL_ON;
56
57         return ret;
58 }
59
60 /*
61  * Map PB_OPERATION_CONTROL_ON to PB_ON_OFF_CONFIG_POLARITY_HIGH.
62  */
63 static int tda38640_write_byte_data(struct i2c_client *client, int page,
64                                     int reg, u8 byte)
65 {
66         const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
67         struct tda38640_data *data = to_tda38640_data(info);
68         int enable, ret;
69
70         if (reg != PMBUS_OPERATION)
71                 return -ENODATA;
72
73         enable = !!(byte & PB_OPERATION_CONTROL_ON);
74
75         byte &= ~PB_OPERATION_CONTROL_ON;
76         ret = pmbus_write_byte_data(client, page, reg, byte);
77         if (ret < 0)
78                 return ret;
79
80         enable ^= data->en_pin_lvl;
81
82         return pmbus_update_byte_data(client, page, PMBUS_ON_OFF_CONFIG,
83                                       PB_ON_OFF_CONFIG_POLARITY_HIGH,
84                                       enable ? 0 : PB_ON_OFF_CONFIG_POLARITY_HIGH);
85 }
86
87 static int svid_mode(struct i2c_client *client, struct tda38640_data *data)
88 {
89         /* PMBUS_MFR_READ(0xD0) + MTP Address offset */
90         u8 write_buf[] = {0xd0, 0x44, 0x00};
91         u8 read_buf[2];
92         int ret, svid;
93         bool off, reg_en_pin_pol;
94
95         struct i2c_msg msgs[2] = {
96                 {
97                         .addr = client->addr,
98                         .flags = 0,
99                         .buf = write_buf,
100                         .len = sizeof(write_buf),
101                 },
102                 {
103                         .addr = client->addr,
104                         .flags = I2C_M_RD,
105                         .buf = read_buf,
106                         .len = sizeof(read_buf),
107                 }
108         };
109
110         ret = i2c_transfer(client->adapter, msgs, 2);
111         if (ret < 0) {
112                 dev_err(&client->dev, "i2c_transfer failed. %d", ret);
113                 return ret;
114         }
115
116         /*
117          * 0x44[15] determines PMBus Operating Mode
118          * If bit is set then it is SVID mode.
119          */
120         svid = !!(read_buf[1] & BIT(7));
121
122         /*
123          * Determine EN pin level for use in SVID mode.
124          * This is done with help of STATUS_BYTE bit 6(OFF) & ON_OFF_CONFIG bit 2(EN pin polarity).
125          */
126         if (svid) {
127                 ret = i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE);
128                 if (ret < 0)
129                         return ret;
130                 off = !!(ret & PB_STATUS_OFF);
131
132                 ret = i2c_smbus_read_byte_data(client, PMBUS_ON_OFF_CONFIG);
133                 if (ret < 0)
134                         return ret;
135                 reg_en_pin_pol = !!(ret & PB_ON_OFF_CONFIG_POLARITY_HIGH);
136                 data->en_pin_lvl = off ^ reg_en_pin_pol;
137         }
138
139         return svid;
140 }
141
142 static struct pmbus_driver_info tda38640_info = {
143         .pages = 1,
144         .format[PSC_VOLTAGE_IN] = linear,
145         .format[PSC_VOLTAGE_OUT] = linear,
146         .format[PSC_CURRENT_OUT] = linear,
147         .format[PSC_CURRENT_IN] = linear,
148         .format[PSC_POWER] = linear,
149         .format[PSC_TEMPERATURE] = linear,
150         .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
151             | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP
152             | PMBUS_HAVE_IIN
153             | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
154             | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
155             | PMBUS_HAVE_POUT | PMBUS_HAVE_PIN,
156 #if IS_ENABLED(CONFIG_SENSORS_TDA38640_REGULATOR)
157         .num_regulators = 1,
158         .reg_desc = tda38640_reg_desc,
159 #endif
160 };
161
162 static int tda38640_probe(struct i2c_client *client)
163 {
164         struct tda38640_data *data;
165         int svid;
166
167         data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
168         if (!data)
169                 return -ENOMEM;
170         memcpy(&data->info, &tda38640_info, sizeof(tda38640_info));
171
172         if (IS_ENABLED(CONFIG_SENSORS_TDA38640_REGULATOR) &&
173             of_property_read_bool(client->dev.of_node, "infineon,en-pin-fixed-level")) {
174                 svid = svid_mode(client, data);
175                 if (svid < 0) {
176                         dev_err_probe(&client->dev, svid, "Could not determine operating mode.");
177                         return svid;
178                 }
179
180                 /*
181                  * Apply ON_OFF_CONFIG workaround as enabling the regulator using the
182                  * OPERATION register doesn't work in SVID mode.
183                  *
184                  * One should configure PMBUS_ON_OFF_CONFIG here, but
185                  * PB_ON_OFF_CONFIG_POWERUP_CONTROL and PB_ON_OFF_CONFIG_EN_PIN_REQ
186                  * are ignored by the device.
187                  * Only PB_ON_OFF_CONFIG_POLARITY_HIGH has an effect.
188                  */
189                 if (svid) {
190                         data->info.read_byte_data = tda38640_read_byte_data;
191                         data->info.write_byte_data = tda38640_write_byte_data;
192                 }
193         }
194         return pmbus_do_probe(client, &data->info);
195 }
196
197 static const struct i2c_device_id tda38640_id[] = {
198         {"tda38640"},
199         {}
200 };
201 MODULE_DEVICE_TABLE(i2c, tda38640_id);
202
203 static const struct of_device_id __maybe_unused tda38640_of_match[] = {
204         { .compatible = "infineon,tda38640"},
205         { },
206 };
207 MODULE_DEVICE_TABLE(of, tda38640_of_match);
208
209 /* This is the driver that will be inserted */
210 static struct i2c_driver tda38640_driver = {
211         .driver = {
212                 .name = "tda38640",
213                 .of_match_table = of_match_ptr(tda38640_of_match),
214         },
215         .probe = tda38640_probe,
216         .id_table = tda38640_id,
217 };
218
219 module_i2c_driver(tda38640_driver);
220
221 MODULE_AUTHOR("Patrick Rudolph <[email protected]>");
222 MODULE_DESCRIPTION("PMBus driver for Infineon TDA38640");
223 MODULE_LICENSE("GPL");
224 MODULE_IMPORT_NS("PMBUS");
This page took 0.038857 seconds and 4 git commands to generate.