Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/extcon/extcon-intel-mrfld.c
26378 views
1
// SPDX-License-Identifier: GPL-2.0
2
/*
3
* extcon driver for Basin Cove PMIC
4
*
5
* Copyright (c) 2019, Intel Corporation.
6
* Author: Andy Shevchenko <[email protected]>
7
*/
8
9
#include <linux/extcon-provider.h>
10
#include <linux/interrupt.h>
11
#include <linux/mfd/intel_soc_pmic.h>
12
#include <linux/mfd/intel_soc_pmic_mrfld.h>
13
#include <linux/mod_devicetable.h>
14
#include <linux/module.h>
15
#include <linux/platform_device.h>
16
#include <linux/regmap.h>
17
18
#include "extcon-intel.h"
19
20
#define BCOVE_USBIDCTRL 0x19
21
#define BCOVE_USBIDCTRL_ID BIT(0)
22
#define BCOVE_USBIDCTRL_ACA BIT(1)
23
#define BCOVE_USBIDCTRL_ALL (BCOVE_USBIDCTRL_ID | BCOVE_USBIDCTRL_ACA)
24
25
#define BCOVE_USBIDSTS 0x1a
26
#define BCOVE_USBIDSTS_GND BIT(0)
27
#define BCOVE_USBIDSTS_RARBRC_MASK GENMASK(2, 1)
28
#define BCOVE_USBIDSTS_RARBRC_SHIFT 1
29
#define BCOVE_USBIDSTS_NO_ACA 0
30
#define BCOVE_USBIDSTS_R_ID_A 1
31
#define BCOVE_USBIDSTS_R_ID_B 2
32
#define BCOVE_USBIDSTS_R_ID_C 3
33
#define BCOVE_USBIDSTS_FLOAT BIT(3)
34
#define BCOVE_USBIDSTS_SHORT BIT(4)
35
36
#define BCOVE_CHGRIRQ_ALL (BCOVE_CHGRIRQ_VBUSDET | BCOVE_CHGRIRQ_DCDET | \
37
BCOVE_CHGRIRQ_BATTDET | BCOVE_CHGRIRQ_USBIDDET)
38
39
#define BCOVE_CHGRCTRL0 0x4b
40
#define BCOVE_CHGRCTRL0_CHGRRESET BIT(0)
41
#define BCOVE_CHGRCTRL0_EMRGCHREN BIT(1)
42
#define BCOVE_CHGRCTRL0_EXTCHRDIS BIT(2)
43
#define BCOVE_CHGRCTRL0_SWCONTROL BIT(3)
44
#define BCOVE_CHGRCTRL0_TTLCK BIT(4)
45
#define BCOVE_CHGRCTRL0_BIT_5 BIT(5)
46
#define BCOVE_CHGRCTRL0_BIT_6 BIT(6)
47
#define BCOVE_CHGRCTRL0_CHR_WDT_NOKICK BIT(7)
48
49
struct mrfld_extcon_data {
50
struct device *dev;
51
struct regmap *regmap;
52
struct extcon_dev *edev;
53
unsigned int status;
54
unsigned int id;
55
};
56
57
static const unsigned int mrfld_extcon_cable[] = {
58
EXTCON_USB,
59
EXTCON_USB_HOST,
60
EXTCON_CHG_USB_SDP,
61
EXTCON_CHG_USB_CDP,
62
EXTCON_CHG_USB_DCP,
63
EXTCON_CHG_USB_ACA,
64
EXTCON_NONE,
65
};
66
67
static int mrfld_extcon_clear(struct mrfld_extcon_data *data, unsigned int reg,
68
unsigned int mask)
69
{
70
return regmap_update_bits(data->regmap, reg, mask, 0x00);
71
}
72
73
static int mrfld_extcon_set(struct mrfld_extcon_data *data, unsigned int reg,
74
unsigned int mask)
75
{
76
return regmap_update_bits(data->regmap, reg, mask, 0xff);
77
}
78
79
static int mrfld_extcon_sw_control(struct mrfld_extcon_data *data, bool enable)
80
{
81
unsigned int mask = BCOVE_CHGRCTRL0_SWCONTROL;
82
struct device *dev = data->dev;
83
int ret;
84
85
if (enable)
86
ret = mrfld_extcon_set(data, BCOVE_CHGRCTRL0, mask);
87
else
88
ret = mrfld_extcon_clear(data, BCOVE_CHGRCTRL0, mask);
89
if (ret)
90
dev_err(dev, "can't set SW control: %d\n", ret);
91
return ret;
92
}
93
94
static int mrfld_extcon_get_id(struct mrfld_extcon_data *data)
95
{
96
struct regmap *regmap = data->regmap;
97
unsigned int id;
98
bool ground;
99
int ret;
100
101
ret = regmap_read(regmap, BCOVE_USBIDSTS, &id);
102
if (ret)
103
return ret;
104
105
if (id & BCOVE_USBIDSTS_FLOAT)
106
return INTEL_USB_ID_FLOAT;
107
108
switch ((id & BCOVE_USBIDSTS_RARBRC_MASK) >> BCOVE_USBIDSTS_RARBRC_SHIFT) {
109
case BCOVE_USBIDSTS_R_ID_A:
110
return INTEL_USB_RID_A;
111
case BCOVE_USBIDSTS_R_ID_B:
112
return INTEL_USB_RID_B;
113
case BCOVE_USBIDSTS_R_ID_C:
114
return INTEL_USB_RID_C;
115
}
116
117
/*
118
* PMIC A0 reports USBIDSTS_GND = 1 for ID_GND,
119
* but PMIC B0 reports USBIDSTS_GND = 0 for ID_GND.
120
* Thus we must check this bit at last.
121
*/
122
ground = id & BCOVE_USBIDSTS_GND;
123
switch ('A' + BCOVE_MAJOR(data->id)) {
124
case 'A':
125
return ground ? INTEL_USB_ID_GND : INTEL_USB_ID_FLOAT;
126
case 'B':
127
return ground ? INTEL_USB_ID_FLOAT : INTEL_USB_ID_GND;
128
}
129
130
/* Unknown or unsupported type */
131
return INTEL_USB_ID_FLOAT;
132
}
133
134
static int mrfld_extcon_role_detect(struct mrfld_extcon_data *data)
135
{
136
unsigned int id;
137
bool usb_host;
138
int ret;
139
140
ret = mrfld_extcon_get_id(data);
141
if (ret < 0)
142
return ret;
143
144
id = ret;
145
146
usb_host = (id == INTEL_USB_ID_GND) || (id == INTEL_USB_RID_A);
147
extcon_set_state_sync(data->edev, EXTCON_USB_HOST, usb_host);
148
149
return 0;
150
}
151
152
static int mrfld_extcon_cable_detect(struct mrfld_extcon_data *data)
153
{
154
struct regmap *regmap = data->regmap;
155
unsigned int status, change;
156
int ret;
157
158
/*
159
* It seems SCU firmware clears the content of BCOVE_CHGRIRQ1
160
* and makes it useless for OS. Instead we compare a previously
161
* stored status to the current one, provided by BCOVE_SCHGRIRQ1.
162
*/
163
ret = regmap_read(regmap, BCOVE_SCHGRIRQ1, &status);
164
if (ret)
165
return ret;
166
167
change = status ^ data->status;
168
if (!change)
169
return -ENODATA;
170
171
if (change & BCOVE_CHGRIRQ_USBIDDET) {
172
ret = mrfld_extcon_role_detect(data);
173
if (ret)
174
return ret;
175
}
176
177
data->status = status;
178
179
return 0;
180
}
181
182
static irqreturn_t mrfld_extcon_interrupt(int irq, void *dev_id)
183
{
184
struct mrfld_extcon_data *data = dev_id;
185
int ret;
186
187
ret = mrfld_extcon_cable_detect(data);
188
189
mrfld_extcon_clear(data, BCOVE_MIRQLVL1, BCOVE_LVL1_CHGR);
190
191
return ret ? IRQ_NONE: IRQ_HANDLED;
192
}
193
194
static int mrfld_extcon_probe(struct platform_device *pdev)
195
{
196
struct device *dev = &pdev->dev;
197
struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);
198
struct regmap *regmap = pmic->regmap;
199
struct mrfld_extcon_data *data;
200
unsigned int status;
201
unsigned int id;
202
int irq, ret;
203
204
irq = platform_get_irq(pdev, 0);
205
if (irq < 0)
206
return irq;
207
208
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
209
if (!data)
210
return -ENOMEM;
211
212
data->dev = dev;
213
data->regmap = regmap;
214
215
data->edev = devm_extcon_dev_allocate(dev, mrfld_extcon_cable);
216
if (IS_ERR(data->edev))
217
return PTR_ERR(data->edev);
218
219
ret = devm_extcon_dev_register(dev, data->edev);
220
if (ret < 0)
221
return dev_err_probe(dev, ret, "can't register extcon device\n");
222
223
ret = devm_request_threaded_irq(dev, irq, NULL, mrfld_extcon_interrupt,
224
IRQF_ONESHOT | IRQF_SHARED, pdev->name,
225
data);
226
if (ret)
227
return dev_err_probe(dev, ret, "can't register IRQ handler\n");
228
229
ret = regmap_read(regmap, BCOVE_ID, &id);
230
if (ret)
231
return dev_err_probe(dev, ret, "can't read PMIC ID\n");
232
233
data->id = id;
234
235
ret = mrfld_extcon_sw_control(data, true);
236
if (ret)
237
return ret;
238
239
/* Get initial state */
240
mrfld_extcon_role_detect(data);
241
242
/*
243
* Cached status value is used for cable detection, see comments
244
* in mrfld_extcon_cable_detect(), we need to sync cached value
245
* with a real state of the hardware.
246
*/
247
regmap_read(regmap, BCOVE_SCHGRIRQ1, &status);
248
data->status = status;
249
250
mrfld_extcon_clear(data, BCOVE_MIRQLVL1, BCOVE_LVL1_CHGR);
251
mrfld_extcon_clear(data, BCOVE_MCHGRIRQ1, BCOVE_CHGRIRQ_ALL);
252
253
mrfld_extcon_set(data, BCOVE_USBIDCTRL, BCOVE_USBIDCTRL_ALL);
254
255
platform_set_drvdata(pdev, data);
256
257
return 0;
258
}
259
260
static void mrfld_extcon_remove(struct platform_device *pdev)
261
{
262
struct mrfld_extcon_data *data = platform_get_drvdata(pdev);
263
264
mrfld_extcon_sw_control(data, false);
265
}
266
267
static const struct platform_device_id mrfld_extcon_id_table[] = {
268
{ .name = "mrfld_bcove_pwrsrc" },
269
{}
270
};
271
MODULE_DEVICE_TABLE(platform, mrfld_extcon_id_table);
272
273
static struct platform_driver mrfld_extcon_driver = {
274
.driver = {
275
.name = "mrfld_bcove_pwrsrc",
276
},
277
.probe = mrfld_extcon_probe,
278
.remove = mrfld_extcon_remove,
279
.id_table = mrfld_extcon_id_table,
280
};
281
module_platform_driver(mrfld_extcon_driver);
282
283
MODULE_AUTHOR("Andy Shevchenko <[email protected]>");
284
MODULE_DESCRIPTION("extcon driver for Intel Merrifield Basin Cove PMIC");
285
MODULE_LICENSE("GPL v2");
286
287