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("vout" , 0), |
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 | reg: 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, value: byte); |
77 | if (ret < 0) |
78 | return ret; |
79 | |
80 | enable ^= data->en_pin_lvl; |
81 | |
82 | return pmbus_update_byte_data(client, page, reg: PMBUS_ON_OFF_CONFIG, |
83 | PB_ON_OFF_CONFIG_POLARITY_HIGH, |
84 | value: 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(adap: client->adapter, msgs, num: 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, command: 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, command: 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(dev: &client->dev, size: 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(np: client->dev.of_node, propname: "infineon,en-pin-fixed-level" )) { |
174 | svid = svid_mode(client, data); |
175 | if (svid < 0) { |
176 | dev_err_probe(dev: &client->dev, err: svid, fmt: "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, info: &data->info); |
195 | } |
196 | |
197 | static const struct i2c_device_id tda38640_id[] = { |
198 | {"tda38640" , 0}, |
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 <patrick.rudolph@9elements.com>" ); |
222 | MODULE_DESCRIPTION("PMBus driver for Infineon TDA38640" ); |
223 | MODULE_LICENSE("GPL" ); |
224 | MODULE_IMPORT_NS(PMBUS); |
225 | |