Commit | Line | Data |
---|---|---|
ce074240 PY |
1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* | |
3 | * Driver for MPS MP5990 Hot-Swap Controller | |
4 | */ | |
5 | ||
6 | #include <linux/i2c.h> | |
7 | #include <linux/module.h> | |
8 | #include <linux/of_device.h> | |
9 | #include "pmbus.h" | |
10 | ||
11 | #define MP5990_EFUSE_CFG (0xC4) | |
12 | #define MP5990_VOUT_FORMAT BIT(9) | |
13 | ||
14 | struct mp5990_data { | |
15 | struct pmbus_driver_info info; | |
16 | u8 vout_mode; | |
17 | u8 vout_linear_exponent; | |
18 | }; | |
19 | ||
20 | #define to_mp5990_data(x) container_of(x, struct mp5990_data, info) | |
21 | ||
22 | static int mp5990_read_byte_data(struct i2c_client *client, int page, int reg) | |
23 | { | |
24 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | |
25 | struct mp5990_data *data = to_mp5990_data(info); | |
26 | ||
27 | switch (reg) { | |
28 | case PMBUS_VOUT_MODE: | |
29 | if (data->vout_mode == linear) { | |
30 | /* | |
31 | * The VOUT format used by the chip is linear11, | |
32 | * not linear16. Report that VOUT is in linear mode | |
33 | * and return exponent value extracted while probing | |
34 | * the chip. | |
35 | */ | |
36 | return data->vout_linear_exponent; | |
37 | } | |
38 | ||
39 | /* | |
40 | * The datasheet does not support the VOUT command, | |
41 | * but the device responds with a default value of 0x17. | |
42 | * In the standard, 0x17 represents linear mode. | |
43 | * Therefore, we should report that VOUT is in direct | |
44 | * format when the chip is configured for it. | |
45 | */ | |
46 | return PB_VOUT_MODE_DIRECT; | |
47 | ||
48 | default: | |
49 | return -ENODATA; | |
50 | } | |
51 | } | |
52 | ||
53 | static int mp5990_read_word_data(struct i2c_client *client, int page, | |
54 | int phase, int reg) | |
55 | { | |
56 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | |
57 | struct mp5990_data *data = to_mp5990_data(info); | |
58 | int ret; | |
59 | s32 mantissa; | |
60 | ||
61 | switch (reg) { | |
62 | case PMBUS_READ_VOUT: | |
63 | ret = pmbus_read_word_data(client, page, phase, reg); | |
64 | if (ret < 0) | |
65 | return ret; | |
66 | /* | |
67 | * Because the VOUT format used by the chip is linear11 and not | |
68 | * linear16, we disregard bits[15:11]. The exponent is reported | |
69 | * as part of the VOUT_MODE command. | |
70 | */ | |
71 | if (data->vout_mode == linear) { | |
72 | mantissa = ((s16)((ret & 0x7ff) << 5)) >> 5; | |
73 | ret = mantissa; | |
74 | } | |
75 | break; | |
76 | default: | |
77 | return -ENODATA; | |
78 | } | |
79 | ||
80 | return ret; | |
81 | } | |
82 | ||
83 | static struct pmbus_driver_info mp5990_info = { | |
84 | .pages = 1, | |
85 | .format[PSC_VOLTAGE_IN] = direct, | |
86 | .format[PSC_VOLTAGE_OUT] = direct, | |
87 | .format[PSC_CURRENT_OUT] = direct, | |
88 | .format[PSC_POWER] = direct, | |
89 | .format[PSC_TEMPERATURE] = direct, | |
90 | .m[PSC_VOLTAGE_IN] = 32, | |
91 | .b[PSC_VOLTAGE_IN] = 0, | |
92 | .R[PSC_VOLTAGE_IN] = 0, | |
93 | .m[PSC_VOLTAGE_OUT] = 32, | |
94 | .b[PSC_VOLTAGE_OUT] = 0, | |
95 | .R[PSC_VOLTAGE_OUT] = 0, | |
96 | .m[PSC_CURRENT_OUT] = 16, | |
97 | .b[PSC_CURRENT_OUT] = 0, | |
98 | .R[PSC_CURRENT_OUT] = 0, | |
99 | .m[PSC_POWER] = 1, | |
100 | .b[PSC_POWER] = 0, | |
101 | .R[PSC_POWER] = 0, | |
102 | .m[PSC_TEMPERATURE] = 1, | |
103 | .b[PSC_TEMPERATURE] = 0, | |
104 | .R[PSC_TEMPERATURE] = 0, | |
105 | .func[0] = | |
106 | PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_PIN | | |
107 | PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT | | |
108 | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, | |
109 | .read_byte_data = mp5990_read_byte_data, | |
110 | .read_word_data = mp5990_read_word_data, | |
111 | }; | |
112 | ||
113 | static int mp5990_probe(struct i2c_client *client) | |
114 | { | |
115 | struct pmbus_driver_info *info; | |
116 | struct mp5990_data *data; | |
117 | int ret; | |
118 | ||
119 | data = devm_kzalloc(&client->dev, sizeof(struct mp5990_data), | |
120 | GFP_KERNEL); | |
121 | if (!data) | |
122 | return -ENOMEM; | |
123 | ||
124 | memcpy(&data->info, &mp5990_info, sizeof(*info)); | |
125 | info = &data->info; | |
126 | ||
127 | /* Read Vout Config */ | |
128 | ret = i2c_smbus_read_word_data(client, MP5990_EFUSE_CFG); | |
129 | if (ret < 0) { | |
130 | dev_err(&client->dev, "Can't get vout mode."); | |
131 | return ret; | |
132 | } | |
133 | ||
134 | /* | |
135 | * EFUSE_CFG (0xC4) bit9=1 is linear mode, bit=0 is direct mode. | |
136 | */ | |
137 | if (ret & MP5990_VOUT_FORMAT) { | |
138 | data->vout_mode = linear; | |
139 | data->info.format[PSC_VOLTAGE_IN] = linear; | |
140 | data->info.format[PSC_VOLTAGE_OUT] = linear; | |
141 | data->info.format[PSC_CURRENT_OUT] = linear; | |
142 | data->info.format[PSC_POWER] = linear; | |
143 | ret = i2c_smbus_read_word_data(client, PMBUS_READ_VOUT); | |
144 | if (ret < 0) { | |
145 | dev_err(&client->dev, "Can't get vout exponent."); | |
146 | return ret; | |
147 | } | |
148 | data->vout_linear_exponent = (u8)((ret >> 11) & 0x1f); | |
149 | } else { | |
150 | data->vout_mode = direct; | |
151 | } | |
152 | return pmbus_do_probe(client, info); | |
153 | } | |
154 | ||
155 | static const struct of_device_id mp5990_of_match[] = { | |
156 | { .compatible = "mps,mp5990" }, | |
157 | {} | |
158 | }; | |
159 | ||
160 | static const struct i2c_device_id mp5990_id[] = { | |
d8a66f36 | 161 | {"mp5990"}, |
ce074240 PY |
162 | { } |
163 | }; | |
164 | MODULE_DEVICE_TABLE(i2c, mp5990_id); | |
165 | ||
166 | static struct i2c_driver mp5990_driver = { | |
167 | .driver = { | |
168 | .name = "mp5990", | |
169 | .of_match_table = mp5990_of_match, | |
170 | }, | |
171 | .probe = mp5990_probe, | |
172 | .id_table = mp5990_id, | |
173 | }; | |
174 | module_i2c_driver(mp5990_driver); | |
175 | ||
176 | MODULE_AUTHOR("Peter Yin <peter.yin@quantatw.com>"); | |
177 | MODULE_DESCRIPTION("PMBus driver for MP5990 HSC"); | |
178 | MODULE_LICENSE("GPL"); | |
179 | MODULE_IMPORT_NS(PMBUS); |