1492929c5SAndy Shevchenko // SPDX-License-Identifier: GPL-2.0
2492929c5SAndy Shevchenko /*
3492929c5SAndy Shevchenko * extcon driver for Basin Cove PMIC
4492929c5SAndy Shevchenko *
5492929c5SAndy Shevchenko * Copyright (c) 2019, Intel Corporation.
6492929c5SAndy Shevchenko * Author: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
7492929c5SAndy Shevchenko */
8492929c5SAndy Shevchenko
9492929c5SAndy Shevchenko #include <linux/extcon-provider.h>
10492929c5SAndy Shevchenko #include <linux/interrupt.h>
11492929c5SAndy Shevchenko #include <linux/mfd/intel_soc_pmic.h>
12492929c5SAndy Shevchenko #include <linux/mfd/intel_soc_pmic_mrfld.h>
13492929c5SAndy Shevchenko #include <linux/mod_devicetable.h>
14492929c5SAndy Shevchenko #include <linux/module.h>
15492929c5SAndy Shevchenko #include <linux/platform_device.h>
16492929c5SAndy Shevchenko #include <linux/regmap.h>
17492929c5SAndy Shevchenko
18492929c5SAndy Shevchenko #include "extcon-intel.h"
19492929c5SAndy Shevchenko
20492929c5SAndy Shevchenko #define BCOVE_USBIDCTRL 0x19
21492929c5SAndy Shevchenko #define BCOVE_USBIDCTRL_ID BIT(0)
22492929c5SAndy Shevchenko #define BCOVE_USBIDCTRL_ACA BIT(1)
23492929c5SAndy Shevchenko #define BCOVE_USBIDCTRL_ALL (BCOVE_USBIDCTRL_ID | BCOVE_USBIDCTRL_ACA)
24492929c5SAndy Shevchenko
25492929c5SAndy Shevchenko #define BCOVE_USBIDSTS 0x1a
26492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_GND BIT(0)
27492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_RARBRC_MASK GENMASK(2, 1)
28492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_RARBRC_SHIFT 1
29492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_NO_ACA 0
30492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_R_ID_A 1
31492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_R_ID_B 2
32492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_R_ID_C 3
33492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_FLOAT BIT(3)
34492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_SHORT BIT(4)
35492929c5SAndy Shevchenko
36492929c5SAndy Shevchenko #define BCOVE_CHGRIRQ_ALL (BCOVE_CHGRIRQ_VBUSDET | BCOVE_CHGRIRQ_DCDET | \
37492929c5SAndy Shevchenko BCOVE_CHGRIRQ_BATTDET | BCOVE_CHGRIRQ_USBIDDET)
38492929c5SAndy Shevchenko
39492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0 0x4b
40492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_CHGRRESET BIT(0)
41492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_EMRGCHREN BIT(1)
42492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_EXTCHRDIS BIT(2)
43492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_SWCONTROL BIT(3)
44492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_TTLCK BIT(4)
45492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_BIT_5 BIT(5)
46492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_BIT_6 BIT(6)
47492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_CHR_WDT_NOKICK BIT(7)
48492929c5SAndy Shevchenko
49492929c5SAndy Shevchenko struct mrfld_extcon_data {
50492929c5SAndy Shevchenko struct device *dev;
51492929c5SAndy Shevchenko struct regmap *regmap;
52492929c5SAndy Shevchenko struct extcon_dev *edev;
53492929c5SAndy Shevchenko unsigned int status;
54492929c5SAndy Shevchenko unsigned int id;
55492929c5SAndy Shevchenko };
56492929c5SAndy Shevchenko
57492929c5SAndy Shevchenko static const unsigned int mrfld_extcon_cable[] = {
58492929c5SAndy Shevchenko EXTCON_USB,
59492929c5SAndy Shevchenko EXTCON_USB_HOST,
60492929c5SAndy Shevchenko EXTCON_CHG_USB_SDP,
61492929c5SAndy Shevchenko EXTCON_CHG_USB_CDP,
62492929c5SAndy Shevchenko EXTCON_CHG_USB_DCP,
63492929c5SAndy Shevchenko EXTCON_CHG_USB_ACA,
64492929c5SAndy Shevchenko EXTCON_NONE,
65492929c5SAndy Shevchenko };
66492929c5SAndy Shevchenko
mrfld_extcon_clear(struct mrfld_extcon_data * data,unsigned int reg,unsigned int mask)67492929c5SAndy Shevchenko static int mrfld_extcon_clear(struct mrfld_extcon_data *data, unsigned int reg,
68492929c5SAndy Shevchenko unsigned int mask)
69492929c5SAndy Shevchenko {
70492929c5SAndy Shevchenko return regmap_update_bits(data->regmap, reg, mask, 0x00);
71492929c5SAndy Shevchenko }
72492929c5SAndy Shevchenko
mrfld_extcon_set(struct mrfld_extcon_data * data,unsigned int reg,unsigned int mask)73492929c5SAndy Shevchenko static int mrfld_extcon_set(struct mrfld_extcon_data *data, unsigned int reg,
74492929c5SAndy Shevchenko unsigned int mask)
75492929c5SAndy Shevchenko {
76492929c5SAndy Shevchenko return regmap_update_bits(data->regmap, reg, mask, 0xff);
77492929c5SAndy Shevchenko }
78492929c5SAndy Shevchenko
mrfld_extcon_sw_control(struct mrfld_extcon_data * data,bool enable)79492929c5SAndy Shevchenko static int mrfld_extcon_sw_control(struct mrfld_extcon_data *data, bool enable)
80492929c5SAndy Shevchenko {
81492929c5SAndy Shevchenko unsigned int mask = BCOVE_CHGRCTRL0_SWCONTROL;
82492929c5SAndy Shevchenko struct device *dev = data->dev;
83492929c5SAndy Shevchenko int ret;
84492929c5SAndy Shevchenko
85492929c5SAndy Shevchenko if (enable)
86492929c5SAndy Shevchenko ret = mrfld_extcon_set(data, BCOVE_CHGRCTRL0, mask);
87492929c5SAndy Shevchenko else
88492929c5SAndy Shevchenko ret = mrfld_extcon_clear(data, BCOVE_CHGRCTRL0, mask);
89492929c5SAndy Shevchenko if (ret)
90492929c5SAndy Shevchenko dev_err(dev, "can't set SW control: %d\n", ret);
91492929c5SAndy Shevchenko return ret;
92492929c5SAndy Shevchenko }
93492929c5SAndy Shevchenko
mrfld_extcon_get_id(struct mrfld_extcon_data * data)94492929c5SAndy Shevchenko static int mrfld_extcon_get_id(struct mrfld_extcon_data *data)
95492929c5SAndy Shevchenko {
96492929c5SAndy Shevchenko struct regmap *regmap = data->regmap;
97492929c5SAndy Shevchenko unsigned int id;
98492929c5SAndy Shevchenko bool ground;
99492929c5SAndy Shevchenko int ret;
100492929c5SAndy Shevchenko
101492929c5SAndy Shevchenko ret = regmap_read(regmap, BCOVE_USBIDSTS, &id);
102492929c5SAndy Shevchenko if (ret)
103492929c5SAndy Shevchenko return ret;
104492929c5SAndy Shevchenko
105492929c5SAndy Shevchenko if (id & BCOVE_USBIDSTS_FLOAT)
106492929c5SAndy Shevchenko return INTEL_USB_ID_FLOAT;
107492929c5SAndy Shevchenko
108492929c5SAndy Shevchenko switch ((id & BCOVE_USBIDSTS_RARBRC_MASK) >> BCOVE_USBIDSTS_RARBRC_SHIFT) {
109492929c5SAndy Shevchenko case BCOVE_USBIDSTS_R_ID_A:
110492929c5SAndy Shevchenko return INTEL_USB_RID_A;
111492929c5SAndy Shevchenko case BCOVE_USBIDSTS_R_ID_B:
112492929c5SAndy Shevchenko return INTEL_USB_RID_B;
113492929c5SAndy Shevchenko case BCOVE_USBIDSTS_R_ID_C:
114492929c5SAndy Shevchenko return INTEL_USB_RID_C;
115492929c5SAndy Shevchenko }
116492929c5SAndy Shevchenko
117492929c5SAndy Shevchenko /*
118492929c5SAndy Shevchenko * PMIC A0 reports USBIDSTS_GND = 1 for ID_GND,
119492929c5SAndy Shevchenko * but PMIC B0 reports USBIDSTS_GND = 0 for ID_GND.
120492929c5SAndy Shevchenko * Thus we must check this bit at last.
121492929c5SAndy Shevchenko */
122492929c5SAndy Shevchenko ground = id & BCOVE_USBIDSTS_GND;
123492929c5SAndy Shevchenko switch ('A' + BCOVE_MAJOR(data->id)) {
124492929c5SAndy Shevchenko case 'A':
125492929c5SAndy Shevchenko return ground ? INTEL_USB_ID_GND : INTEL_USB_ID_FLOAT;
126492929c5SAndy Shevchenko case 'B':
127492929c5SAndy Shevchenko return ground ? INTEL_USB_ID_FLOAT : INTEL_USB_ID_GND;
128492929c5SAndy Shevchenko }
129492929c5SAndy Shevchenko
130492929c5SAndy Shevchenko /* Unknown or unsupported type */
131492929c5SAndy Shevchenko return INTEL_USB_ID_FLOAT;
132492929c5SAndy Shevchenko }
133492929c5SAndy Shevchenko
mrfld_extcon_role_detect(struct mrfld_extcon_data * data)134492929c5SAndy Shevchenko static int mrfld_extcon_role_detect(struct mrfld_extcon_data *data)
135492929c5SAndy Shevchenko {
136492929c5SAndy Shevchenko unsigned int id;
137492929c5SAndy Shevchenko bool usb_host;
138492929c5SAndy Shevchenko int ret;
139492929c5SAndy Shevchenko
140492929c5SAndy Shevchenko ret = mrfld_extcon_get_id(data);
141492929c5SAndy Shevchenko if (ret < 0)
142492929c5SAndy Shevchenko return ret;
143492929c5SAndy Shevchenko
144492929c5SAndy Shevchenko id = ret;
145492929c5SAndy Shevchenko
146492929c5SAndy Shevchenko usb_host = (id == INTEL_USB_ID_GND) || (id == INTEL_USB_RID_A);
147492929c5SAndy Shevchenko extcon_set_state_sync(data->edev, EXTCON_USB_HOST, usb_host);
148492929c5SAndy Shevchenko
149492929c5SAndy Shevchenko return 0;
150492929c5SAndy Shevchenko }
151492929c5SAndy Shevchenko
mrfld_extcon_cable_detect(struct mrfld_extcon_data * data)152492929c5SAndy Shevchenko static int mrfld_extcon_cable_detect(struct mrfld_extcon_data *data)
153492929c5SAndy Shevchenko {
154492929c5SAndy Shevchenko struct regmap *regmap = data->regmap;
155492929c5SAndy Shevchenko unsigned int status, change;
156492929c5SAndy Shevchenko int ret;
157492929c5SAndy Shevchenko
158492929c5SAndy Shevchenko /*
159492929c5SAndy Shevchenko * It seems SCU firmware clears the content of BCOVE_CHGRIRQ1
160492929c5SAndy Shevchenko * and makes it useless for OS. Instead we compare a previously
161492929c5SAndy Shevchenko * stored status to the current one, provided by BCOVE_SCHGRIRQ1.
162492929c5SAndy Shevchenko */
163492929c5SAndy Shevchenko ret = regmap_read(regmap, BCOVE_SCHGRIRQ1, &status);
164492929c5SAndy Shevchenko if (ret)
165492929c5SAndy Shevchenko return ret;
166492929c5SAndy Shevchenko
167492929c5SAndy Shevchenko change = status ^ data->status;
168492929c5SAndy Shevchenko if (!change)
169492929c5SAndy Shevchenko return -ENODATA;
170492929c5SAndy Shevchenko
171492929c5SAndy Shevchenko if (change & BCOVE_CHGRIRQ_USBIDDET) {
172492929c5SAndy Shevchenko ret = mrfld_extcon_role_detect(data);
173492929c5SAndy Shevchenko if (ret)
174492929c5SAndy Shevchenko return ret;
175492929c5SAndy Shevchenko }
176492929c5SAndy Shevchenko
177492929c5SAndy Shevchenko data->status = status;
178492929c5SAndy Shevchenko
179492929c5SAndy Shevchenko return 0;
180492929c5SAndy Shevchenko }
181492929c5SAndy Shevchenko
mrfld_extcon_interrupt(int irq,void * dev_id)182492929c5SAndy Shevchenko static irqreturn_t mrfld_extcon_interrupt(int irq, void *dev_id)
183492929c5SAndy Shevchenko {
184492929c5SAndy Shevchenko struct mrfld_extcon_data *data = dev_id;
185492929c5SAndy Shevchenko int ret;
186492929c5SAndy Shevchenko
187492929c5SAndy Shevchenko ret = mrfld_extcon_cable_detect(data);
188492929c5SAndy Shevchenko
189492929c5SAndy Shevchenko mrfld_extcon_clear(data, BCOVE_MIRQLVL1, BCOVE_LVL1_CHGR);
190492929c5SAndy Shevchenko
191492929c5SAndy Shevchenko return ret ? IRQ_NONE: IRQ_HANDLED;
192492929c5SAndy Shevchenko }
193492929c5SAndy Shevchenko
mrfld_extcon_probe(struct platform_device * pdev)194492929c5SAndy Shevchenko static int mrfld_extcon_probe(struct platform_device *pdev)
195492929c5SAndy Shevchenko {
196492929c5SAndy Shevchenko struct device *dev = &pdev->dev;
197492929c5SAndy Shevchenko struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);
198492929c5SAndy Shevchenko struct regmap *regmap = pmic->regmap;
199492929c5SAndy Shevchenko struct mrfld_extcon_data *data;
200*ecb5bdffSFerry Toth unsigned int status;
201492929c5SAndy Shevchenko unsigned int id;
202492929c5SAndy Shevchenko int irq, ret;
203492929c5SAndy Shevchenko
204492929c5SAndy Shevchenko irq = platform_get_irq(pdev, 0);
205492929c5SAndy Shevchenko if (irq < 0)
206492929c5SAndy Shevchenko return irq;
207492929c5SAndy Shevchenko
208492929c5SAndy Shevchenko data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
209492929c5SAndy Shevchenko if (!data)
210492929c5SAndy Shevchenko return -ENOMEM;
211492929c5SAndy Shevchenko
212492929c5SAndy Shevchenko data->dev = dev;
213492929c5SAndy Shevchenko data->regmap = regmap;
214492929c5SAndy Shevchenko
215492929c5SAndy Shevchenko data->edev = devm_extcon_dev_allocate(dev, mrfld_extcon_cable);
216492929c5SAndy Shevchenko if (IS_ERR(data->edev))
217492929c5SAndy Shevchenko return -ENOMEM;
218492929c5SAndy Shevchenko
219492929c5SAndy Shevchenko ret = devm_extcon_dev_register(dev, data->edev);
220492929c5SAndy Shevchenko if (ret < 0) {
221492929c5SAndy Shevchenko dev_err(dev, "can't register extcon device: %d\n", ret);
222492929c5SAndy Shevchenko return ret;
223492929c5SAndy Shevchenko }
224492929c5SAndy Shevchenko
225492929c5SAndy Shevchenko ret = devm_request_threaded_irq(dev, irq, NULL, mrfld_extcon_interrupt,
226492929c5SAndy Shevchenko IRQF_ONESHOT | IRQF_SHARED, pdev->name,
227492929c5SAndy Shevchenko data);
228492929c5SAndy Shevchenko if (ret) {
229492929c5SAndy Shevchenko dev_err(dev, "can't register IRQ handler: %d\n", ret);
230492929c5SAndy Shevchenko return ret;
231492929c5SAndy Shevchenko }
232492929c5SAndy Shevchenko
233492929c5SAndy Shevchenko ret = regmap_read(regmap, BCOVE_ID, &id);
234492929c5SAndy Shevchenko if (ret) {
235492929c5SAndy Shevchenko dev_err(dev, "can't read PMIC ID: %d\n", ret);
236492929c5SAndy Shevchenko return ret;
237492929c5SAndy Shevchenko }
238492929c5SAndy Shevchenko
239492929c5SAndy Shevchenko data->id = id;
240492929c5SAndy Shevchenko
241492929c5SAndy Shevchenko ret = mrfld_extcon_sw_control(data, true);
242492929c5SAndy Shevchenko if (ret)
243492929c5SAndy Shevchenko return ret;
244492929c5SAndy Shevchenko
245492929c5SAndy Shevchenko /* Get initial state */
246492929c5SAndy Shevchenko mrfld_extcon_role_detect(data);
247492929c5SAndy Shevchenko
248*ecb5bdffSFerry Toth /*
249*ecb5bdffSFerry Toth * Cached status value is used for cable detection, see comments
250*ecb5bdffSFerry Toth * in mrfld_extcon_cable_detect(), we need to sync cached value
251*ecb5bdffSFerry Toth * with a real state of the hardware.
252*ecb5bdffSFerry Toth */
253*ecb5bdffSFerry Toth regmap_read(regmap, BCOVE_SCHGRIRQ1, &status);
254*ecb5bdffSFerry Toth data->status = status;
255*ecb5bdffSFerry Toth
256492929c5SAndy Shevchenko mrfld_extcon_clear(data, BCOVE_MIRQLVL1, BCOVE_LVL1_CHGR);
257492929c5SAndy Shevchenko mrfld_extcon_clear(data, BCOVE_MCHGRIRQ1, BCOVE_CHGRIRQ_ALL);
258492929c5SAndy Shevchenko
259492929c5SAndy Shevchenko mrfld_extcon_set(data, BCOVE_USBIDCTRL, BCOVE_USBIDCTRL_ALL);
260492929c5SAndy Shevchenko
261492929c5SAndy Shevchenko platform_set_drvdata(pdev, data);
262492929c5SAndy Shevchenko
263492929c5SAndy Shevchenko return 0;
264492929c5SAndy Shevchenko }
265492929c5SAndy Shevchenko
mrfld_extcon_remove(struct platform_device * pdev)266492929c5SAndy Shevchenko static int mrfld_extcon_remove(struct platform_device *pdev)
267492929c5SAndy Shevchenko {
268492929c5SAndy Shevchenko struct mrfld_extcon_data *data = platform_get_drvdata(pdev);
269492929c5SAndy Shevchenko
270492929c5SAndy Shevchenko mrfld_extcon_sw_control(data, false);
271492929c5SAndy Shevchenko
272492929c5SAndy Shevchenko return 0;
273492929c5SAndy Shevchenko }
274492929c5SAndy Shevchenko
275492929c5SAndy Shevchenko static const struct platform_device_id mrfld_extcon_id_table[] = {
276492929c5SAndy Shevchenko { .name = "mrfld_bcove_pwrsrc" },
277492929c5SAndy Shevchenko {}
278492929c5SAndy Shevchenko };
279492929c5SAndy Shevchenko MODULE_DEVICE_TABLE(platform, mrfld_extcon_id_table);
280492929c5SAndy Shevchenko
281492929c5SAndy Shevchenko static struct platform_driver mrfld_extcon_driver = {
282492929c5SAndy Shevchenko .driver = {
283492929c5SAndy Shevchenko .name = "mrfld_bcove_pwrsrc",
284492929c5SAndy Shevchenko },
285492929c5SAndy Shevchenko .probe = mrfld_extcon_probe,
286492929c5SAndy Shevchenko .remove = mrfld_extcon_remove,
287492929c5SAndy Shevchenko .id_table = mrfld_extcon_id_table,
288492929c5SAndy Shevchenko };
289492929c5SAndy Shevchenko module_platform_driver(mrfld_extcon_driver);
290492929c5SAndy Shevchenko
291492929c5SAndy Shevchenko MODULE_AUTHOR("Andy Shevchenko <andriy.shevchenko@linux.intel.com>");
292492929c5SAndy Shevchenko MODULE_DESCRIPTION("extcon driver for Intel Merrifield Basin Cove PMIC");
293492929c5SAndy Shevchenko MODULE_LICENSE("GPL v2");
294