summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomasz Figa <tomasz.figa@gmail.com>2019-06-21 07:13:52 -0400
committerChanwoo Choi <cw00.choi@samsung.com>2019-06-22 08:34:51 -0400
commitbad5b5e707a58db9a65d59f7706314012a8e3219 (patch)
treeb1d6cdaa2073d697575df320b88e443b7909c195
parentfd757dbac5f6c42f45b1a29a354255045a655a27 (diff)
extcon: Add fsa9480 extcon driver
This patch adds extcon driver for Fairchild Semiconductor FSA9480 microUSB switch. Signed-off-by: Tomasz Figa <tomasz.figa@gmail.com> Signed-off-by: Jonathan Bakker <xc-racer2@live.ca> Signed-off-by: Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com> Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
-rw-r--r--drivers/extcon/Kconfig12
-rw-r--r--drivers/extcon/Makefile1
-rw-r--r--drivers/extcon/extcon-fsa9480.c395
3 files changed, 408 insertions, 0 deletions
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
index 6f5af4196b8d..8aa83c6274a0 100644
--- a/drivers/extcon/Kconfig
+++ b/drivers/extcon/Kconfig
@@ -37,6 +37,18 @@ config EXTCON_AXP288
37 Say Y here to enable support for USB peripheral detection 37 Say Y here to enable support for USB peripheral detection
38 and USB MUX switching by X-Power AXP288 PMIC. 38 and USB MUX switching by X-Power AXP288 PMIC.
39 39
40config EXTCON_FSA9480
41 tristate "FSA9480 EXTCON Support"
42 depends on INPUT
43 select IRQ_DOMAIN
44 select REGMAP_I2C
45 help
46 If you say yes here you get support for the Fairchild Semiconductor
47 FSA9480 microUSB switch and accessory detector chip. The FSA9480 is a USB
48 port accessory detector and switch. The FSA9480 is fully controlled using
49 I2C and enables USB data, stereo and mono audio, video, microphone
50 and UART data to use a common connector port.
51
40config EXTCON_GPIO 52config EXTCON_GPIO
41 tristate "GPIO extcon support" 53 tristate "GPIO extcon support"
42 depends on GPIOLIB || COMPILE_TEST 54 depends on GPIOLIB || COMPILE_TEST
diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
index d3941a735df3..52096fd8a216 100644
--- a/drivers/extcon/Makefile
+++ b/drivers/extcon/Makefile
@@ -8,6 +8,7 @@ extcon-core-objs += extcon.o devres.o
8obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o 8obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o
9obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o 9obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
10obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o 10obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o
11obj-$(CONFIG_EXTCON_FSA9480) += extcon-fsa9480.o
11obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o 12obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
12obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o 13obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
13obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o 14obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o
diff --git a/drivers/extcon/extcon-fsa9480.c b/drivers/extcon/extcon-fsa9480.c
new file mode 100644
index 000000000000..350fb34abfa0
--- /dev/null
+++ b/drivers/extcon/extcon-fsa9480.c
@@ -0,0 +1,395 @@
1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * extcon-fsa9480.c - Fairchild Semiconductor FSA9480 extcon driver
4 *
5 * Copyright (c) 2019 Tomasz Figa <tomasz.figa@gmail.com>
6 *
7 * Loosely based on old fsa9480 misc-device driver.
8 */
9
10#include <linux/kernel.h>
11#include <linux/module.h>
12#include <linux/types.h>
13#include <linux/i2c.h>
14#include <linux/slab.h>
15#include <linux/bitops.h>
16#include <linux/interrupt.h>
17#include <linux/err.h>
18#include <linux/platform_device.h>
19#include <linux/kobject.h>
20#include <linux/extcon-provider.h>
21#include <linux/irqdomain.h>
22#include <linux/regmap.h>
23
24/* FSA9480 I2C registers */
25#define FSA9480_REG_DEVID 0x01
26#define FSA9480_REG_CTRL 0x02
27#define FSA9480_REG_INT1 0x03
28#define FSA9480_REG_INT2 0x04
29#define FSA9480_REG_INT1_MASK 0x05
30#define FSA9480_REG_INT2_MASK 0x06
31#define FSA9480_REG_ADC 0x07
32#define FSA9480_REG_TIMING1 0x08
33#define FSA9480_REG_TIMING2 0x09
34#define FSA9480_REG_DEV_T1 0x0a
35#define FSA9480_REG_DEV_T2 0x0b
36#define FSA9480_REG_BTN1 0x0c
37#define FSA9480_REG_BTN2 0x0d
38#define FSA9480_REG_CK 0x0e
39#define FSA9480_REG_CK_INT1 0x0f
40#define FSA9480_REG_CK_INT2 0x10
41#define FSA9480_REG_CK_INTMASK1 0x11
42#define FSA9480_REG_CK_INTMASK2 0x12
43#define FSA9480_REG_MANSW1 0x13
44#define FSA9480_REG_MANSW2 0x14
45#define FSA9480_REG_END 0x15
46
47/* Control */
48#define CON_SWITCH_OPEN (1 << 4)
49#define CON_RAW_DATA (1 << 3)
50#define CON_MANUAL_SW (1 << 2)
51#define CON_WAIT (1 << 1)
52#define CON_INT_MASK (1 << 0)
53#define CON_MASK (CON_SWITCH_OPEN | CON_RAW_DATA | \
54 CON_MANUAL_SW | CON_WAIT)
55
56/* Device Type 1 */
57#define DEV_USB_OTG 7
58#define DEV_DEDICATED_CHG 6
59#define DEV_USB_CHG 5
60#define DEV_CAR_KIT 4
61#define DEV_UART 3
62#define DEV_USB 2
63#define DEV_AUDIO_2 1
64#define DEV_AUDIO_1 0
65
66#define DEV_T1_USB_MASK (DEV_USB_OTG | DEV_USB)
67#define DEV_T1_UART_MASK (DEV_UART)
68#define DEV_T1_CHARGER_MASK (DEV_DEDICATED_CHG | DEV_USB_CHG)
69
70/* Device Type 2 */
71#define DEV_AV 14
72#define DEV_TTY 13
73#define DEV_PPD 12
74#define DEV_JIG_UART_OFF 11
75#define DEV_JIG_UART_ON 10
76#define DEV_JIG_USB_OFF 9
77#define DEV_JIG_USB_ON 8
78
79#define DEV_T2_USB_MASK (DEV_JIG_USB_OFF | DEV_JIG_USB_ON)
80#define DEV_T2_UART_MASK (DEV_JIG_UART_OFF | DEV_JIG_UART_ON)
81#define DEV_T2_JIG_MASK (DEV_JIG_USB_OFF | DEV_JIG_USB_ON | \
82 DEV_JIG_UART_OFF | DEV_JIG_UART_ON)
83
84/*
85 * Manual Switch
86 * D- [7:5] / D+ [4:2]
87 * 000: Open all / 001: USB / 010: AUDIO / 011: UART / 100: V_AUDIO
88 */
89#define SW_VAUDIO ((4 << 5) | (4 << 2))
90#define SW_UART ((3 << 5) | (3 << 2))
91#define SW_AUDIO ((2 << 5) | (2 << 2))
92#define SW_DHOST ((1 << 5) | (1 << 2))
93#define SW_AUTO ((0 << 5) | (0 << 2))
94
95/* Interrupt 1 */
96#define INT1_MASK (0xff << 0)
97#define INT_DETACH (1 << 1)
98#define INT_ATTACH (1 << 0)
99
100/* Interrupt 2 mask */
101#define INT2_MASK (0x1f << 0)
102
103/* Timing Set 1 */
104#define TIMING1_ADC_500MS (0x6 << 0)
105
106struct fsa9480_usbsw {
107 struct device *dev;
108 struct regmap *regmap;
109 struct extcon_dev *edev;
110 u16 cable;
111};
112
113static const unsigned int fsa9480_extcon_cable[] = {
114 EXTCON_USB_HOST,
115 EXTCON_USB,
116 EXTCON_CHG_USB_DCP,
117 EXTCON_CHG_USB_SDP,
118 EXTCON_CHG_USB_ACA,
119 EXTCON_JACK_LINE_OUT,
120 EXTCON_JACK_VIDEO_OUT,
121 EXTCON_JIG,
122
123 EXTCON_NONE,
124};
125
126static const u64 cable_types[] = {
127 [DEV_USB_OTG] = BIT_ULL(EXTCON_USB_HOST),
128 [DEV_DEDICATED_CHG] = BIT_ULL(EXTCON_USB) | BIT_ULL(EXTCON_CHG_USB_DCP),
129 [DEV_USB_CHG] = BIT_ULL(EXTCON_USB) | BIT_ULL(EXTCON_CHG_USB_SDP),
130 [DEV_CAR_KIT] = BIT_ULL(EXTCON_USB) | BIT_ULL(EXTCON_CHG_USB_SDP)
131 | BIT_ULL(EXTCON_JACK_LINE_OUT),
132 [DEV_UART] = BIT_ULL(EXTCON_JIG),
133 [DEV_USB] = BIT_ULL(EXTCON_USB) | BIT_ULL(EXTCON_CHG_USB_SDP),
134 [DEV_AUDIO_2] = BIT_ULL(EXTCON_JACK_LINE_OUT),
135 [DEV_AUDIO_1] = BIT_ULL(EXTCON_JACK_LINE_OUT),
136 [DEV_AV] = BIT_ULL(EXTCON_JACK_LINE_OUT)
137 | BIT_ULL(EXTCON_JACK_VIDEO_OUT),
138 [DEV_TTY] = BIT_ULL(EXTCON_JIG),
139 [DEV_PPD] = BIT_ULL(EXTCON_JACK_LINE_OUT) | BIT_ULL(EXTCON_CHG_USB_ACA),
140 [DEV_JIG_UART_OFF] = BIT_ULL(EXTCON_JIG),
141 [DEV_JIG_UART_ON] = BIT_ULL(EXTCON_JIG),
142 [DEV_JIG_USB_OFF] = BIT_ULL(EXTCON_USB) | BIT_ULL(EXTCON_JIG),
143 [DEV_JIG_USB_ON] = BIT_ULL(EXTCON_USB) | BIT_ULL(EXTCON_JIG),
144};
145
146/* Define regmap configuration of FSA9480 for I2C communication */
147static bool fsa9480_volatile_reg(struct device *dev, unsigned int reg)
148{
149 switch (reg) {
150 case FSA9480_REG_INT1_MASK:
151 return true;
152 default:
153 break;
154 }
155 return false;
156}
157
158static const struct regmap_config fsa9480_regmap_config = {
159 .reg_bits = 8,
160 .val_bits = 8,
161 .volatile_reg = fsa9480_volatile_reg,
162 .max_register = FSA9480_REG_END,
163};
164
165static int fsa9480_write_reg(struct fsa9480_usbsw *usbsw, int reg, int value)
166{
167 int ret;
168
169 ret = regmap_write(usbsw->regmap, reg, value);
170 if (ret < 0)
171 dev_err(usbsw->dev, "%s: err %d\n", __func__, ret);
172
173 return ret;
174}
175
176static int fsa9480_read_reg(struct fsa9480_usbsw *usbsw, int reg)
177{
178 int ret, val;
179
180 ret = regmap_read(usbsw->regmap, reg, &val);
181 if (ret < 0) {
182 dev_err(usbsw->dev, "%s: err %d\n", __func__, ret);
183 return ret;
184 }
185
186 return val;
187}
188
189static int fsa9480_read_irq(struct fsa9480_usbsw *usbsw, int *value)
190{
191 u8 regs[2];
192 int ret;
193
194 ret = regmap_bulk_read(usbsw->regmap, FSA9480_REG_INT1, regs, 2);
195 if (ret < 0)
196 dev_err(usbsw->dev, "%s: err %d\n", __func__, ret);
197
198 *value = regs[1] << 8 | regs[0];
199 return ret;
200}
201
202static void fsa9480_handle_change(struct fsa9480_usbsw *usbsw,
203 u16 mask, bool attached)
204{
205 while (mask) {
206 int dev = fls64(mask) - 1;
207 u64 cables = cable_types[dev];
208
209 while (cables) {
210 int cable = fls64(cables) - 1;
211
212 extcon_set_state_sync(usbsw->edev, cable, attached);
213 cables &= ~BIT_ULL(cable);
214 }
215
216 mask &= ~BIT_ULL(dev);
217 }
218}
219
220static void fsa9480_detect_dev(struct fsa9480_usbsw *usbsw)
221{
222 int val1, val2;
223 u16 val;
224
225 val1 = fsa9480_read_reg(usbsw, FSA9480_REG_DEV_T1);
226 val2 = fsa9480_read_reg(usbsw, FSA9480_REG_DEV_T2);
227 if (val1 < 0 || val2 < 0) {
228 dev_err(usbsw->dev, "%s: failed to read registers", __func__);
229 return;
230 }
231 val = val2 << 8 | val1;
232
233 dev_info(usbsw->dev, "dev1: 0x%x, dev2: 0x%x\n", val1, val2);
234
235 /* handle detached cables first */
236 fsa9480_handle_change(usbsw, usbsw->cable & ~val, false);
237
238 /* then handle attached ones */
239 fsa9480_handle_change(usbsw, val & ~usbsw->cable, true);
240
241 usbsw->cable = val;
242}
243
244static irqreturn_t fsa9480_irq_handler(int irq, void *data)
245{
246 struct fsa9480_usbsw *usbsw = data;
247 int intr = 0;
248
249 /* clear interrupt */
250 fsa9480_read_irq(usbsw, &intr);
251 if (!intr)
252 return IRQ_NONE;
253
254 /* device detection */
255 fsa9480_detect_dev(usbsw);
256
257 return IRQ_HANDLED;
258}
259
260static int fsa9480_probe(struct i2c_client *client,
261 const struct i2c_device_id *id)
262{
263 struct fsa9480_usbsw *info;
264 int ret;
265
266 if (!client->irq) {
267 dev_err(&client->dev, "no interrupt provided\n");
268 return -EINVAL;
269 }
270
271 info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL);
272 if (!info)
273 return -ENOMEM;
274 info->dev = &client->dev;
275
276 i2c_set_clientdata(client, info);
277
278 /* External connector */
279 info->edev = devm_extcon_dev_allocate(info->dev,
280 fsa9480_extcon_cable);
281 if (IS_ERR(info->edev)) {
282 dev_err(info->dev, "failed to allocate memory for extcon\n");
283 ret = -ENOMEM;
284 return ret;
285 }
286
287 ret = devm_extcon_dev_register(info->dev, info->edev);
288 if (ret) {
289 dev_err(info->dev, "failed to register extcon device\n");
290 return ret;
291 }
292
293 info->regmap = devm_regmap_init_i2c(client, &fsa9480_regmap_config);
294 if (IS_ERR(info->regmap)) {
295 ret = PTR_ERR(info->regmap);
296 dev_err(info->dev, "failed to allocate register map: %d\n",
297 ret);
298 return ret;
299 }
300
301 /* ADC Detect Time: 500ms */
302 fsa9480_write_reg(info, FSA9480_REG_TIMING1, TIMING1_ADC_500MS);
303
304 /* configure automatic switching */
305 fsa9480_write_reg(info, FSA9480_REG_CTRL, CON_MASK);
306
307 /* unmask interrupt (attach/detach only) */
308 fsa9480_write_reg(info, FSA9480_REG_INT1_MASK,
309 INT1_MASK & ~(INT_ATTACH | INT_DETACH));
310 fsa9480_write_reg(info, FSA9480_REG_INT2_MASK, INT2_MASK);
311
312 ret = devm_request_threaded_irq(info->dev, client->irq, NULL,
313 fsa9480_irq_handler,
314 IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
315 "fsa9480", info);
316 if (ret) {
317 dev_err(info->dev, "failed to request IRQ\n");
318 return ret;
319 }
320
321 device_init_wakeup(info->dev, true);
322 fsa9480_detect_dev(info);
323
324 return 0;
325}
326
327static int fsa9480_remove(struct i2c_client *client)
328{
329 return 0;
330}
331
332#ifdef CONFIG_PM_SLEEP
333static int fsa9480_suspend(struct device *dev)
334{
335 struct i2c_client *client = to_i2c_client(dev);
336
337 if (device_may_wakeup(&client->dev) && client->irq)
338 enable_irq_wake(client->irq);
339
340 return 0;
341}
342
343static int fsa9480_resume(struct device *dev)
344{
345 struct i2c_client *client = to_i2c_client(dev);
346
347 if (device_may_wakeup(&client->dev) && client->irq)
348 disable_irq_wake(client->irq);
349
350 return 0;
351}
352#endif
353
354static const struct dev_pm_ops fsa9480_pm_ops = {
355 SET_SYSTEM_SLEEP_PM_OPS(fsa9480_suspend, fsa9480_resume)
356};
357
358static const struct i2c_device_id fsa9480_id[] = {
359 { "fsa9480", 0 },
360 {}
361};
362MODULE_DEVICE_TABLE(i2c, fsa9480_id);
363
364static const struct of_device_id fsa9480_of_match[] = {
365 { .compatible = "fcs,fsa9480", },
366 { },
367};
368MODULE_DEVICE_TABLE(of, fsa9480_of_match);
369
370static struct i2c_driver fsa9480_i2c_driver = {
371 .driver = {
372 .name = "fsa9480",
373 .pm = &fsa9480_pm_ops,
374 .of_match_table = fsa9480_of_match,
375 },
376 .probe = fsa9480_probe,
377 .remove = fsa9480_remove,
378 .id_table = fsa9480_id,
379};
380
381static int __init fsa9480_module_init(void)
382{
383 return i2c_add_driver(&fsa9480_i2c_driver);
384}
385subsys_initcall(fsa9480_module_init);
386
387static void __exit fsa9480_module_exit(void)
388{
389 i2c_del_driver(&fsa9480_i2c_driver);
390}
391module_exit(fsa9480_module_exit);
392
393MODULE_DESCRIPTION("Fairchild Semiconductor FSA9480 extcon driver");
394MODULE_AUTHOR("Tomasz Figa <tomasz.figa@gmail.com>");
395MODULE_LICENSE("GPL");