Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0-or-later
2
3#include <linux/device.h>
4#include <linux/devm-helpers.h>
5#include <linux/delay.h>
6#include <linux/err.h>
7#include <linux/extcon-provider.h>
8#include <linux/i2c.h>
9#include <linux/mod_devicetable.h>
10#include <linux/interrupt.h>
11#include <linux/module.h>
12#include <linux/pm.h>
13#include <linux/regmap.h>
14
15/* I2C addresses of MUIC internal registers */
16#define MAX14526_DEVICE_ID 0x00
17#define MAX14526_ID 0x02
18
19/* CONTROL_1 register masks */
20#define MAX14526_CONTROL_1 0x01
21#define ID_2P2 BIT(6)
22#define ID_620 BIT(5)
23#define ID_200 BIT(4)
24#define VLDO BIT(3)
25#define SEMREN BIT(2)
26#define ADC_EN BIT(1)
27#define CP_EN BIT(0)
28
29/* CONTROL_2 register masks */
30#define MAX14526_CONTROL_2 0x02
31#define INTPOL BIT(7)
32#define INT_EN BIT(6)
33#define MIC_LP BIT(5)
34#define CP_AUD BIT(4)
35#define CHG_TYPE BIT(1)
36#define USB_DET_DIS BIT(0)
37
38/* SW_CONTROL register masks */
39#define MAX14526_SW_CONTROL 0x03
40#define SW_DATA 0x00
41#define SW_UART 0x01
42#define SW_AUDIO 0x02
43#define SW_OPEN 0x07
44
45/* INT_STATUS register masks */
46#define MAX14526_INT_STAT 0x04
47#define CHGDET BIT(7)
48#define MR_COMP BIT(6)
49#define SENDEND BIT(5)
50#define V_VBUS BIT(4)
51
52/* STATUS register masks */
53#define MAX14526_STATUS 0x05
54#define CPORT BIT(7)
55#define CHPORT BIT(6)
56#define C1COMP BIT(0)
57
58enum max14526_idno_resistance {
59 MAX14526_GND,
60 MAX14526_24KOHM,
61 MAX14526_56KOHM,
62 MAX14526_100KOHM,
63 MAX14526_130KOHM,
64 MAX14526_180KOHM,
65 MAX14526_240KOHM,
66 MAX14526_330KOHM,
67 MAX14526_430KOHM,
68 MAX14526_620KOHM,
69 MAX14526_910KOHM,
70 MAX14526_OPEN
71};
72
73enum max14526_field_idx {
74 VENDOR_ID, CHIP_REV, /* DEVID */
75 DM, DP, /* SW_CONTROL */
76 MAX14526_N_REGMAP_FIELDS
77};
78
79static const struct reg_field max14526_reg_field[MAX14526_N_REGMAP_FIELDS] = {
80 [VENDOR_ID] = REG_FIELD(MAX14526_DEVICE_ID, 4, 7),
81 [CHIP_REV] = REG_FIELD(MAX14526_DEVICE_ID, 0, 3),
82 [DM] = REG_FIELD(MAX14526_SW_CONTROL, 0, 2),
83 [DP] = REG_FIELD(MAX14526_SW_CONTROL, 3, 5),
84};
85
86struct max14526_data {
87 struct i2c_client *client;
88 struct extcon_dev *edev;
89
90 struct regmap *regmap;
91 struct regmap_field *rfield[MAX14526_N_REGMAP_FIELDS];
92
93 int last_state;
94 int cable;
95};
96
97enum max14526_muic_modes {
98 MAX14526_OTG = MAX14526_GND, /* no power */
99 MAX14526_MHL = MAX14526_56KOHM, /* no power */
100 MAX14526_OTG_Y = MAX14526_GND | V_VBUS,
101 MAX14526_MHL_CHG = MAX14526_GND | V_VBUS | CHGDET,
102 MAX14526_NONE = MAX14526_OPEN,
103 MAX14526_USB = MAX14526_OPEN | V_VBUS,
104 MAX14526_CHG = MAX14526_OPEN | V_VBUS | CHGDET,
105};
106
107static const unsigned int max14526_extcon_cable[] = {
108 EXTCON_USB,
109 EXTCON_USB_HOST,
110 EXTCON_CHG_USB_FAST,
111 EXTCON_DISP_MHL,
112 EXTCON_NONE,
113};
114
115static int max14526_ap_usb_mode(struct max14526_data *priv)
116{
117 struct device *dev = &priv->client->dev;
118 int ret;
119
120 /* Enable USB Path */
121 ret = regmap_field_write(priv->rfield[DM], SW_DATA);
122 if (ret)
123 return ret;
124
125 ret = regmap_field_write(priv->rfield[DP], SW_DATA);
126 if (ret)
127 return ret;
128
129 /* Enable 200K, Charger Pump and ADC */
130 ret = regmap_write(priv->regmap, MAX14526_CONTROL_1,
131 ID_200 | ADC_EN | CP_EN);
132 if (ret)
133 return ret;
134
135 dev_dbg(dev, "AP USB mode set\n");
136
137 return 0;
138}
139
140static irqreturn_t max14526_interrupt(int irq, void *dev_id)
141{
142 struct max14526_data *priv = dev_id;
143 struct device *dev = &priv->client->dev;
144 int state, ret;
145
146 /*
147 * Upon an MUIC IRQ (MUIC_INT_N falls), wait at least 70ms
148 * before reading INT_STAT and STATUS. After the reads,
149 * MUIC_INT_N returns to high (but the INT_STAT and STATUS
150 * contents will be held).
151 */
152 msleep(100);
153
154 ret = regmap_read(priv->regmap, MAX14526_INT_STAT, &state);
155 if (ret)
156 dev_err(dev, "failed to read MUIC state %d\n", ret);
157
158 if (state == priv->last_state)
159 return IRQ_HANDLED;
160
161 /* Detach previous device */
162 extcon_set_state_sync(priv->edev, priv->cable, false);
163
164 switch (state) {
165 case MAX14526_USB:
166 priv->cable = EXTCON_USB;
167 break;
168
169 case MAX14526_CHG:
170 priv->cable = EXTCON_CHG_USB_FAST;
171 break;
172
173 case MAX14526_OTG:
174 case MAX14526_OTG_Y:
175 priv->cable = EXTCON_USB_HOST;
176 break;
177
178 case MAX14526_MHL:
179 case MAX14526_MHL_CHG:
180 priv->cable = EXTCON_DISP_MHL;
181 break;
182
183 case MAX14526_NONE:
184 default:
185 priv->cable = EXTCON_NONE;
186 break;
187 }
188
189 extcon_set_state_sync(priv->edev, priv->cable, true);
190
191 priv->last_state = state;
192
193 return IRQ_HANDLED;
194}
195
196static const struct regmap_config max14526_regmap_config = {
197 .reg_bits = 8,
198 .val_bits = 8,
199 .max_register = MAX14526_STATUS,
200};
201
202static int max14526_probe(struct i2c_client *client)
203{
204 struct device *dev = &client->dev;
205 struct max14526_data *priv;
206 int ret, dev_id, rev, i;
207
208 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
209 if (!priv)
210 return -ENOMEM;
211
212 priv->client = client;
213 i2c_set_clientdata(client, priv);
214
215 priv->regmap = devm_regmap_init_i2c(client, &max14526_regmap_config);
216 if (IS_ERR(priv->regmap))
217 return dev_err_probe(dev, PTR_ERR(priv->regmap), "cannot allocate regmap\n");
218
219 for (i = 0; i < MAX14526_N_REGMAP_FIELDS; i++) {
220 priv->rfield[i] = devm_regmap_field_alloc(dev, priv->regmap,
221 max14526_reg_field[i]);
222 if (IS_ERR(priv->rfield[i]))
223 return dev_err_probe(dev, PTR_ERR(priv->rfield[i]),
224 "cannot allocate regmap field\n");
225 }
226
227 /* Detect if MUIC version is supported */
228 ret = regmap_field_read(priv->rfield[VENDOR_ID], &dev_id);
229 if (ret)
230 return dev_err_probe(dev, ret, "failed to read MUIC ID\n");
231
232 regmap_field_read(priv->rfield[CHIP_REV], &rev);
233
234 if (dev_id == MAX14526_ID)
235 dev_info(dev, "detected MAX14526 MUIC with id 0x%x, rev 0x%x\n", dev_id, rev);
236 else
237 dev_err_probe(dev, -EINVAL, "MUIC vendor id 0x%X is not recognized\n", dev_id);
238
239 priv->edev = devm_extcon_dev_allocate(dev, max14526_extcon_cable);
240 if (IS_ERR(priv->edev))
241 return dev_err_probe(dev, (IS_ERR(priv->edev)),
242 "failed to allocate extcon device\n");
243
244 ret = devm_extcon_dev_register(dev, priv->edev);
245 if (ret < 0)
246 return dev_err_probe(dev, ret, "failed to register extcon device\n");
247
248 ret = max14526_ap_usb_mode(priv);
249 if (ret < 0)
250 return dev_err_probe(dev, ret, "failed to set AP USB mode\n");
251
252 regmap_write_bits(priv->regmap, MAX14526_CONTROL_2, INT_EN, INT_EN);
253 regmap_write_bits(priv->regmap, MAX14526_CONTROL_2, USB_DET_DIS, (u32)~USB_DET_DIS);
254
255 ret = devm_request_threaded_irq(dev, client->irq, NULL, &max14526_interrupt,
256 IRQF_ONESHOT | IRQF_SHARED, client->name, priv);
257 if (ret)
258 return dev_err_probe(dev, ret, "failed to register IRQ\n");
259
260 irq_wake_thread(client->irq, priv);
261
262 return 0;
263}
264
265static int max14526_resume(struct device *dev)
266{
267 struct i2c_client *client = to_i2c_client(dev);
268 struct max14526_data *priv = i2c_get_clientdata(client);
269
270 irq_wake_thread(client->irq, priv);
271
272 return 0;
273}
274
275static DEFINE_SIMPLE_DEV_PM_OPS(max14526_pm_ops, NULL, max14526_resume);
276
277static const struct of_device_id max14526_match[] = {
278 { .compatible = "maxim,max14526" },
279 { /* sentinel */ }
280};
281MODULE_DEVICE_TABLE(of, max14526_match);
282
283static const struct i2c_device_id max14526_id[] = {
284 { "max14526" },
285 { }
286};
287MODULE_DEVICE_TABLE(i2c, max14526_id);
288
289static struct i2c_driver max14526_driver = {
290 .driver = {
291 .name = "max14526",
292 .of_match_table = max14526_match,
293 .pm = &max14526_pm_ops,
294 },
295 .probe = max14526_probe,
296 .id_table = max14526_id,
297};
298module_i2c_driver(max14526_driver);
299
300MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
301MODULE_DESCRIPTION("MAX14526 extcon driver to support MUIC");
302MODULE_LICENSE("GPL");