Commit | Line | Data |
---|---|---|
17a924bf HG |
1 | /* |
2 | * Intel CHT Whiskey Cove PMIC I2C Master driver | |
3 | * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> | |
4 | * | |
5 | * Based on various non upstream patches to support the CHT Whiskey Cove PMIC: | |
6 | * Copyright (C) 2011 - 2014 Intel Corporation. All rights reserved. | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU General Public License version | |
10 | * 2 as published by the Free Software Foundation, or (at your option) | |
11 | * any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | */ | |
18 | ||
0224d45c | 19 | #include <linux/acpi.h> |
17a924bf HG |
20 | #include <linux/completion.h> |
21 | #include <linux/delay.h> | |
22 | #include <linux/i2c.h> | |
23 | #include <linux/interrupt.h> | |
24 | #include <linux/irq.h> | |
25 | #include <linux/irqdomain.h> | |
26 | #include <linux/mfd/intel_soc_pmic.h> | |
27 | #include <linux/module.h> | |
28 | #include <linux/platform_device.h> | |
0224d45c | 29 | #include <linux/power/bq24190_charger.h> |
17a924bf HG |
30 | #include <linux/slab.h> |
31 | ||
32 | #define CHT_WC_I2C_CTRL 0x5e24 | |
33 | #define CHT_WC_I2C_CTRL_WR BIT(0) | |
34 | #define CHT_WC_I2C_CTRL_RD BIT(1) | |
35 | #define CHT_WC_I2C_CLIENT_ADDR 0x5e25 | |
36 | #define CHT_WC_I2C_REG_OFFSET 0x5e26 | |
37 | #define CHT_WC_I2C_WRDATA 0x5e27 | |
38 | #define CHT_WC_I2C_RDDATA 0x5e28 | |
39 | ||
40 | #define CHT_WC_EXTCHGRIRQ 0x6e0a | |
41 | #define CHT_WC_EXTCHGRIRQ_CLIENT_IRQ BIT(0) | |
42 | #define CHT_WC_EXTCHGRIRQ_WRITE_IRQ BIT(1) | |
43 | #define CHT_WC_EXTCHGRIRQ_READ_IRQ BIT(2) | |
44 | #define CHT_WC_EXTCHGRIRQ_NACK_IRQ BIT(3) | |
45 | #define CHT_WC_EXTCHGRIRQ_ADAP_IRQMASK ((u8)GENMASK(3, 1)) | |
46 | #define CHT_WC_EXTCHGRIRQ_MSK 0x6e17 | |
47 | ||
48 | struct cht_wc_i2c_adap { | |
49 | struct i2c_adapter adapter; | |
50 | wait_queue_head_t wait; | |
51 | struct irq_chip irqchip; | |
a5a46bd0 | 52 | struct mutex adap_lock; |
17a924bf HG |
53 | struct mutex irqchip_lock; |
54 | struct regmap *regmap; | |
55 | struct irq_domain *irq_domain; | |
56 | struct i2c_client *client; | |
57 | int client_irq; | |
58 | u8 irq_mask; | |
59 | u8 old_irq_mask; | |
8de60c63 HG |
60 | int read_data; |
61 | bool io_error; | |
17a924bf HG |
62 | bool done; |
63 | }; | |
64 | ||
65 | static irqreturn_t cht_wc_i2c_adap_thread_handler(int id, void *data) | |
66 | { | |
67 | struct cht_wc_i2c_adap *adap = data; | |
68 | int ret, reg; | |
69 | ||
a5a46bd0 HG |
70 | mutex_lock(&adap->adap_lock); |
71 | ||
17a924bf HG |
72 | /* Read IRQs */ |
73 | ret = regmap_read(adap->regmap, CHT_WC_EXTCHGRIRQ, ®); | |
74 | if (ret) { | |
75 | dev_err(&adap->adapter.dev, "Error reading extchgrirq reg\n"); | |
a5a46bd0 | 76 | mutex_unlock(&adap->adap_lock); |
17a924bf HG |
77 | return IRQ_NONE; |
78 | } | |
79 | ||
80 | reg &= ~adap->irq_mask; | |
81 | ||
8de60c63 HG |
82 | /* Reads must be acked after reading the received data. */ |
83 | ret = regmap_read(adap->regmap, CHT_WC_I2C_RDDATA, &adap->read_data); | |
84 | if (ret) | |
85 | adap->io_error = true; | |
86 | ||
17a924bf HG |
87 | /* |
88 | * Immediately ack IRQs, so that if new IRQs arrives while we're | |
89 | * handling the previous ones our irq will re-trigger when we're done. | |
90 | */ | |
91 | ret = regmap_write(adap->regmap, CHT_WC_EXTCHGRIRQ, reg); | |
92 | if (ret) | |
93 | dev_err(&adap->adapter.dev, "Error writing extchgrirq reg\n"); | |
94 | ||
a5a46bd0 | 95 | if (reg & CHT_WC_EXTCHGRIRQ_ADAP_IRQMASK) { |
8de60c63 | 96 | adap->io_error |= !!(reg & CHT_WC_EXTCHGRIRQ_NACK_IRQ); |
a5a46bd0 HG |
97 | adap->done = true; |
98 | } | |
99 | ||
100 | mutex_unlock(&adap->adap_lock); | |
101 | ||
102 | if (reg & CHT_WC_EXTCHGRIRQ_ADAP_IRQMASK) | |
103 | wake_up(&adap->wait); | |
104 | ||
17a924bf HG |
105 | /* |
106 | * Do NOT use handle_nested_irq here, the client irq handler will | |
107 | * likely want to do i2c transfers and the i2c controller uses this | |
108 | * interrupt handler as well, so running the client irq handler from | |
109 | * this thread will cause things to lock up. | |
110 | */ | |
111 | if (reg & CHT_WC_EXTCHGRIRQ_CLIENT_IRQ) { | |
112 | /* | |
113 | * generic_handle_irq expects local IRQs to be disabled | |
114 | * as normally it is called from interrupt context. | |
115 | */ | |
116 | local_irq_disable(); | |
117 | generic_handle_irq(adap->client_irq); | |
118 | local_irq_enable(); | |
119 | } | |
120 | ||
17a924bf HG |
121 | return IRQ_HANDLED; |
122 | } | |
123 | ||
124 | static u32 cht_wc_i2c_adap_master_func(struct i2c_adapter *adap) | |
125 | { | |
126 | /* This i2c adapter only supports SMBUS byte transfers */ | |
127 | return I2C_FUNC_SMBUS_BYTE_DATA; | |
128 | } | |
129 | ||
130 | static int cht_wc_i2c_adap_smbus_xfer(struct i2c_adapter *_adap, u16 addr, | |
131 | unsigned short flags, char read_write, | |
132 | u8 command, int size, | |
133 | union i2c_smbus_data *data) | |
134 | { | |
135 | struct cht_wc_i2c_adap *adap = i2c_get_adapdata(_adap); | |
8de60c63 | 136 | int ret; |
17a924bf | 137 | |
a5a46bd0 | 138 | mutex_lock(&adap->adap_lock); |
8de60c63 | 139 | adap->io_error = false; |
17a924bf | 140 | adap->done = false; |
a5a46bd0 | 141 | mutex_unlock(&adap->adap_lock); |
17a924bf HG |
142 | |
143 | ret = regmap_write(adap->regmap, CHT_WC_I2C_CLIENT_ADDR, addr); | |
144 | if (ret) | |
145 | return ret; | |
146 | ||
147 | if (read_write == I2C_SMBUS_WRITE) { | |
148 | ret = regmap_write(adap->regmap, CHT_WC_I2C_WRDATA, data->byte); | |
149 | if (ret) | |
150 | return ret; | |
151 | } | |
152 | ||
153 | ret = regmap_write(adap->regmap, CHT_WC_I2C_REG_OFFSET, command); | |
154 | if (ret) | |
155 | return ret; | |
156 | ||
157 | ret = regmap_write(adap->regmap, CHT_WC_I2C_CTRL, | |
158 | (read_write == I2C_SMBUS_WRITE) ? | |
159 | CHT_WC_I2C_CTRL_WR : CHT_WC_I2C_CTRL_RD); | |
160 | if (ret) | |
161 | return ret; | |
162 | ||
ed109401 HG |
163 | ret = wait_event_timeout(adap->wait, adap->done, msecs_to_jiffies(30)); |
164 | if (ret == 0) { | |
165 | /* | |
166 | * The CHT GPIO controller serializes all IRQs, sometimes | |
167 | * causing significant delays, check status manually. | |
168 | */ | |
169 | cht_wc_i2c_adap_thread_handler(0, adap); | |
170 | if (!adap->done) | |
171 | return -ETIMEDOUT; | |
172 | } | |
17a924bf | 173 | |
a5a46bd0 HG |
174 | ret = 0; |
175 | mutex_lock(&adap->adap_lock); | |
8de60c63 | 176 | if (adap->io_error) |
a5a46bd0 | 177 | ret = -EIO; |
8de60c63 HG |
178 | else if (read_write == I2C_SMBUS_READ) |
179 | data->byte = adap->read_data; | |
a5a46bd0 | 180 | mutex_unlock(&adap->adap_lock); |
17a924bf | 181 | |
a5a46bd0 | 182 | return ret; |
17a924bf HG |
183 | } |
184 | ||
185 | static const struct i2c_algorithm cht_wc_i2c_adap_algo = { | |
186 | .functionality = cht_wc_i2c_adap_master_func, | |
187 | .smbus_xfer = cht_wc_i2c_adap_smbus_xfer, | |
188 | }; | |
189 | ||
190 | /**** irqchip for the client connected to the extchgr i2c adapter ****/ | |
191 | static void cht_wc_i2c_irq_lock(struct irq_data *data) | |
192 | { | |
193 | struct cht_wc_i2c_adap *adap = irq_data_get_irq_chip_data(data); | |
194 | ||
195 | mutex_lock(&adap->irqchip_lock); | |
196 | } | |
197 | ||
198 | static void cht_wc_i2c_irq_sync_unlock(struct irq_data *data) | |
199 | { | |
200 | struct cht_wc_i2c_adap *adap = irq_data_get_irq_chip_data(data); | |
201 | int ret; | |
202 | ||
203 | if (adap->irq_mask != adap->old_irq_mask) { | |
204 | ret = regmap_write(adap->regmap, CHT_WC_EXTCHGRIRQ_MSK, | |
205 | adap->irq_mask); | |
206 | if (ret == 0) | |
207 | adap->old_irq_mask = adap->irq_mask; | |
208 | else | |
209 | dev_err(&adap->adapter.dev, "Error writing EXTCHGRIRQ_MSK\n"); | |
210 | } | |
211 | ||
212 | mutex_unlock(&adap->irqchip_lock); | |
213 | } | |
214 | ||
215 | static void cht_wc_i2c_irq_enable(struct irq_data *data) | |
216 | { | |
217 | struct cht_wc_i2c_adap *adap = irq_data_get_irq_chip_data(data); | |
218 | ||
219 | adap->irq_mask &= ~CHT_WC_EXTCHGRIRQ_CLIENT_IRQ; | |
220 | } | |
221 | ||
222 | static void cht_wc_i2c_irq_disable(struct irq_data *data) | |
223 | { | |
224 | struct cht_wc_i2c_adap *adap = irq_data_get_irq_chip_data(data); | |
225 | ||
226 | adap->irq_mask |= CHT_WC_EXTCHGRIRQ_CLIENT_IRQ; | |
227 | } | |
228 | ||
229 | static const struct irq_chip cht_wc_i2c_irq_chip = { | |
230 | .irq_bus_lock = cht_wc_i2c_irq_lock, | |
231 | .irq_bus_sync_unlock = cht_wc_i2c_irq_sync_unlock, | |
232 | .irq_disable = cht_wc_i2c_irq_disable, | |
233 | .irq_enable = cht_wc_i2c_irq_enable, | |
234 | .name = "cht_wc_ext_chrg_irq_chip", | |
235 | }; | |
236 | ||
0224d45c HG |
237 | static const char * const bq24190_suppliers[] = { "fusb302-typec-source" }; |
238 | ||
17a924bf | 239 | static const struct property_entry bq24190_props[] = { |
0224d45c | 240 | PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_suppliers), |
17a924bf HG |
241 | PROPERTY_ENTRY_BOOL("omit-battery-class"), |
242 | PROPERTY_ENTRY_BOOL("disable-reset"), | |
243 | { } | |
244 | }; | |
245 | ||
0224d45c HG |
246 | static struct regulator_consumer_supply fusb302_consumer = { |
247 | .supply = "vbus", | |
248 | /* Must match fusb302 dev_name in intel_cht_int33fe.c */ | |
249 | .dev_name = "i2c-fusb302", | |
250 | }; | |
251 | ||
252 | static const struct regulator_init_data bq24190_vbus_init_data = { | |
253 | .constraints = { | |
254 | /* The name is used in intel_cht_int33fe.c do not change. */ | |
255 | .name = "cht_wc_usb_typec_vbus", | |
256 | .valid_ops_mask = REGULATOR_CHANGE_STATUS, | |
257 | }, | |
258 | .consumer_supplies = &fusb302_consumer, | |
259 | .num_consumer_supplies = 1, | |
260 | }; | |
261 | ||
262 | static struct bq24190_platform_data bq24190_pdata = { | |
263 | .regulator_init_data = &bq24190_vbus_init_data, | |
264 | }; | |
265 | ||
17a924bf HG |
266 | static int cht_wc_i2c_adap_i2c_probe(struct platform_device *pdev) |
267 | { | |
268 | struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); | |
269 | struct cht_wc_i2c_adap *adap; | |
270 | struct i2c_board_info board_info = { | |
271 | .type = "bq24190", | |
272 | .addr = 0x6b, | |
0224d45c | 273 | .dev_name = "bq24190", |
17a924bf | 274 | .properties = bq24190_props, |
0224d45c | 275 | .platform_data = &bq24190_pdata, |
17a924bf | 276 | }; |
8de60c63 | 277 | int ret, reg, irq; |
17a924bf HG |
278 | |
279 | irq = platform_get_irq(pdev, 0); | |
280 | if (irq < 0) { | |
281 | dev_err(&pdev->dev, "Error missing irq resource\n"); | |
282 | return -EINVAL; | |
283 | } | |
284 | ||
285 | adap = devm_kzalloc(&pdev->dev, sizeof(*adap), GFP_KERNEL); | |
286 | if (!adap) | |
287 | return -ENOMEM; | |
288 | ||
289 | init_waitqueue_head(&adap->wait); | |
a5a46bd0 | 290 | mutex_init(&adap->adap_lock); |
17a924bf HG |
291 | mutex_init(&adap->irqchip_lock); |
292 | adap->irqchip = cht_wc_i2c_irq_chip; | |
293 | adap->regmap = pmic->regmap; | |
294 | adap->adapter.owner = THIS_MODULE; | |
295 | adap->adapter.class = I2C_CLASS_HWMON; | |
296 | adap->adapter.algo = &cht_wc_i2c_adap_algo; | |
297 | strlcpy(adap->adapter.name, "PMIC I2C Adapter", | |
298 | sizeof(adap->adapter.name)); | |
299 | adap->adapter.dev.parent = &pdev->dev; | |
300 | ||
301 | /* Clear and activate i2c-adapter interrupts, disable client IRQ */ | |
302 | adap->old_irq_mask = adap->irq_mask = ~CHT_WC_EXTCHGRIRQ_ADAP_IRQMASK; | |
8de60c63 HG |
303 | |
304 | ret = regmap_read(adap->regmap, CHT_WC_I2C_RDDATA, ®); | |
305 | if (ret) | |
306 | return ret; | |
307 | ||
17a924bf HG |
308 | ret = regmap_write(adap->regmap, CHT_WC_EXTCHGRIRQ, ~adap->irq_mask); |
309 | if (ret) | |
310 | return ret; | |
311 | ||
312 | ret = regmap_write(adap->regmap, CHT_WC_EXTCHGRIRQ_MSK, adap->irq_mask); | |
313 | if (ret) | |
314 | return ret; | |
315 | ||
316 | /* Alloc and register client IRQ */ | |
317 | adap->irq_domain = irq_domain_add_linear(pdev->dev.of_node, 1, | |
318 | &irq_domain_simple_ops, NULL); | |
319 | if (!adap->irq_domain) | |
320 | return -ENOMEM; | |
321 | ||
322 | adap->client_irq = irq_create_mapping(adap->irq_domain, 0); | |
323 | if (!adap->client_irq) { | |
324 | ret = -ENOMEM; | |
325 | goto remove_irq_domain; | |
326 | } | |
327 | ||
328 | irq_set_chip_data(adap->client_irq, adap); | |
329 | irq_set_chip_and_handler(adap->client_irq, &adap->irqchip, | |
330 | handle_simple_irq); | |
331 | ||
332 | ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, | |
333 | cht_wc_i2c_adap_thread_handler, | |
334 | IRQF_ONESHOT, "PMIC I2C Adapter", adap); | |
335 | if (ret) | |
336 | goto remove_irq_domain; | |
337 | ||
338 | i2c_set_adapdata(&adap->adapter, adap); | |
339 | ret = i2c_add_adapter(&adap->adapter); | |
340 | if (ret) | |
341 | goto remove_irq_domain; | |
342 | ||
0224d45c HG |
343 | /* |
344 | * Normally the Whiskey Cove PMIC is paired with a TI bq24292i charger, | |
345 | * connected to this i2c bus, and a max17047 fuel-gauge and a fusb302 | |
346 | * USB Type-C controller connected to another i2c bus. In this setup | |
347 | * the max17047 and fusb302 devices are enumerated through an INT33FE | |
348 | * ACPI device. If this device is present register an i2c-client for | |
349 | * the TI bq24292i charger. | |
350 | */ | |
351 | if (acpi_dev_present("INT33FE", NULL, -1)) { | |
352 | board_info.irq = adap->client_irq; | |
353 | adap->client = i2c_new_device(&adap->adapter, &board_info); | |
354 | if (!adap->client) { | |
355 | ret = -ENOMEM; | |
356 | goto del_adapter; | |
357 | } | |
17a924bf HG |
358 | } |
359 | ||
360 | platform_set_drvdata(pdev, adap); | |
361 | return 0; | |
362 | ||
363 | del_adapter: | |
364 | i2c_del_adapter(&adap->adapter); | |
365 | remove_irq_domain: | |
366 | irq_domain_remove(adap->irq_domain); | |
367 | return ret; | |
368 | } | |
369 | ||
370 | static int cht_wc_i2c_adap_i2c_remove(struct platform_device *pdev) | |
371 | { | |
372 | struct cht_wc_i2c_adap *adap = platform_get_drvdata(pdev); | |
373 | ||
0224d45c HG |
374 | if (adap->client) |
375 | i2c_unregister_device(adap->client); | |
17a924bf HG |
376 | i2c_del_adapter(&adap->adapter); |
377 | irq_domain_remove(adap->irq_domain); | |
378 | ||
379 | return 0; | |
380 | } | |
381 | ||
04271ce9 | 382 | static const struct platform_device_id cht_wc_i2c_adap_id_table[] = { |
17a924bf HG |
383 | { .name = "cht_wcove_ext_chgr" }, |
384 | {}, | |
385 | }; | |
386 | MODULE_DEVICE_TABLE(platform, cht_wc_i2c_adap_id_table); | |
387 | ||
5ca21c13 | 388 | static struct platform_driver cht_wc_i2c_adap_driver = { |
17a924bf HG |
389 | .probe = cht_wc_i2c_adap_i2c_probe, |
390 | .remove = cht_wc_i2c_adap_i2c_remove, | |
391 | .driver = { | |
392 | .name = "cht_wcove_ext_chgr", | |
393 | }, | |
394 | .id_table = cht_wc_i2c_adap_id_table, | |
395 | }; | |
396 | module_platform_driver(cht_wc_i2c_adap_driver); | |
397 | ||
398 | MODULE_DESCRIPTION("Intel CHT Whiskey Cove PMIC I2C Master driver"); | |
399 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); | |
400 | MODULE_LICENSE("GPL"); |