Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Tahvo USB transceiver driver
  4 *
  5 * Copyright (C) 2005-2006 Nokia Corporation
  6 *
  7 * Parts copied from isp1301_omap.c.
  8 * Copyright (C) 2004 Texas Instruments
  9 * Copyright (C) 2004 David Brownell
 10 *
 11 * Original driver written by Juha Yrjölä, Tony Lindgren and Timo Teräs.
 12 * Modified for Retu/Tahvo MFD by Aaro Koskinen.
 13 */
 14
 15#include <linux/io.h>
 16#include <linux/clk.h>
 17#include <linux/usb.h>
 18#include <linux/extcon-provider.h>
 19#include <linux/kernel.h>
 20#include <linux/module.h>
 21#include <linux/usb/otg.h>
 22#include <linux/mfd/retu.h>
 23#include <linux/usb/gadget.h>
 24#include <linux/platform_device.h>
 25
 26#define DRIVER_NAME     "tahvo-usb"
 27
 28#define TAHVO_REG_IDSR	0x02
 29#define TAHVO_REG_USBR	0x06
 30
 31#define USBR_SLAVE_CONTROL	(1 << 8)
 32#define USBR_VPPVIO_SW		(1 << 7)
 33#define USBR_SPEED		(1 << 6)
 34#define USBR_REGOUT		(1 << 5)
 35#define USBR_MASTER_SW2		(1 << 4)
 36#define USBR_MASTER_SW1		(1 << 3)
 37#define USBR_SLAVE_SW		(1 << 2)
 38#define USBR_NSUSPEND		(1 << 1)
 39#define USBR_SEMODE		(1 << 0)
 40
 41#define TAHVO_MODE_HOST		0
 42#define TAHVO_MODE_PERIPHERAL	1
 43
 44struct tahvo_usb {
 45	struct platform_device	*pt_dev;
 46	struct usb_phy		phy;
 47	int			vbus_state;
 48	struct mutex		serialize;
 49	struct clk		*ick;
 50	int			irq;
 51	int			tahvo_mode;
 52	struct extcon_dev	*extcon;
 53};
 54
 55static const unsigned int tahvo_cable[] = {
 56	EXTCON_USB,
 57	EXTCON_USB_HOST,
 58
 59	EXTCON_NONE,
 60};
 61
 62static ssize_t vbus_show(struct device *device,
 63			       struct device_attribute *attr, char *buf)
 64{
 65	struct tahvo_usb *tu = dev_get_drvdata(device);
 66	return sprintf(buf, "%s\n", tu->vbus_state ? "on" : "off");
 67}
 68static DEVICE_ATTR_RO(vbus);
 69
 70static void check_vbus_state(struct tahvo_usb *tu)
 71{
 72	struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
 73	int reg, prev_state;
 74
 75	reg = retu_read(rdev, TAHVO_REG_IDSR);
 76	if (reg & TAHVO_STAT_VBUS) {
 77		switch (tu->phy.otg->state) {
 78		case OTG_STATE_B_IDLE:
 79			/* Enable the gadget driver */
 80			if (tu->phy.otg->gadget)
 81				usb_gadget_vbus_connect(tu->phy.otg->gadget);
 82			tu->phy.otg->state = OTG_STATE_B_PERIPHERAL;
 83			usb_phy_set_event(&tu->phy, USB_EVENT_ENUMERATED);
 84			break;
 85		case OTG_STATE_A_IDLE:
 86			/*
 87			 * Session is now valid assuming the USB hub is driving
 88			 * Vbus.
 89			 */
 90			tu->phy.otg->state = OTG_STATE_A_HOST;
 91			break;
 92		default:
 93			break;
 94		}
 95		dev_info(&tu->pt_dev->dev, "USB cable connected\n");
 96	} else {
 97		switch (tu->phy.otg->state) {
 98		case OTG_STATE_B_PERIPHERAL:
 99			if (tu->phy.otg->gadget)
100				usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
101			tu->phy.otg->state = OTG_STATE_B_IDLE;
102			usb_phy_set_event(&tu->phy, USB_EVENT_NONE);
103			break;
104		case OTG_STATE_A_HOST:
105			tu->phy.otg->state = OTG_STATE_A_IDLE;
106			break;
107		default:
108			break;
109		}
110		dev_info(&tu->pt_dev->dev, "USB cable disconnected\n");
111	}
112
113	prev_state = tu->vbus_state;
114	tu->vbus_state = reg & TAHVO_STAT_VBUS;
115	if (prev_state != tu->vbus_state) {
116		extcon_set_state_sync(tu->extcon, EXTCON_USB, tu->vbus_state);
117		sysfs_notify(&tu->pt_dev->dev.kobj, NULL, "vbus_state");
118	}
119}
120
121static void tahvo_usb_become_host(struct tahvo_usb *tu)
122{
123	struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
124
125	extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST, true);
126
127	/* Power up the transceiver in USB host mode */
128	retu_write(rdev, TAHVO_REG_USBR, USBR_REGOUT | USBR_NSUSPEND |
129		   USBR_MASTER_SW2 | USBR_MASTER_SW1);
130	tu->phy.otg->state = OTG_STATE_A_IDLE;
131
132	check_vbus_state(tu);
133}
134
135static void tahvo_usb_stop_host(struct tahvo_usb *tu)
136{
137	tu->phy.otg->state = OTG_STATE_A_IDLE;
138}
139
140static void tahvo_usb_become_peripheral(struct tahvo_usb *tu)
141{
142	struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
143
144	extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST, false);
145
146	/* Power up transceiver and set it in USB peripheral mode */
147	retu_write(rdev, TAHVO_REG_USBR, USBR_SLAVE_CONTROL | USBR_REGOUT |
148		   USBR_NSUSPEND | USBR_SLAVE_SW);
149	tu->phy.otg->state = OTG_STATE_B_IDLE;
150
151	check_vbus_state(tu);
152}
153
154static void tahvo_usb_stop_peripheral(struct tahvo_usb *tu)
155{
156	if (tu->phy.otg->gadget)
157		usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
158	tu->phy.otg->state = OTG_STATE_B_IDLE;
159}
160
161static void tahvo_usb_power_off(struct tahvo_usb *tu)
162{
163	struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
164
165	/* Disable gadget controller if any */
166	if (tu->phy.otg->gadget)
167		usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
168
169	/* Power off transceiver */
170	retu_write(rdev, TAHVO_REG_USBR, 0);
171	tu->phy.otg->state = OTG_STATE_UNDEFINED;
172}
173
174static int tahvo_usb_set_suspend(struct usb_phy *dev, int suspend)
175{
176	struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, phy);
177	struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
178	u16 w;
179
180	dev_dbg(&tu->pt_dev->dev, "%s\n", __func__);
181
182	w = retu_read(rdev, TAHVO_REG_USBR);
183	if (suspend)
184		w &= ~USBR_NSUSPEND;
185	else
186		w |= USBR_NSUSPEND;
187	retu_write(rdev, TAHVO_REG_USBR, w);
188
189	return 0;
190}
191
192static int tahvo_usb_set_host(struct usb_otg *otg, struct usb_bus *host)
193{
194	struct tahvo_usb *tu = container_of(otg->usb_phy, struct tahvo_usb,
195					    phy);
196
197	dev_dbg(&tu->pt_dev->dev, "%s %p\n", __func__, host);
198
199	mutex_lock(&tu->serialize);
200
201	if (host == NULL) {
202		if (tu->tahvo_mode == TAHVO_MODE_HOST)
203			tahvo_usb_power_off(tu);
204		otg->host = NULL;
205		mutex_unlock(&tu->serialize);
206		return 0;
207	}
208
209	if (tu->tahvo_mode == TAHVO_MODE_HOST) {
210		otg->host = NULL;
211		tahvo_usb_become_host(tu);
212	}
213
214	otg->host = host;
215
216	mutex_unlock(&tu->serialize);
217
218	return 0;
219}
220
221static int tahvo_usb_set_peripheral(struct usb_otg *otg,
222				    struct usb_gadget *gadget)
223{
224	struct tahvo_usb *tu = container_of(otg->usb_phy, struct tahvo_usb,
225					    phy);
226
227	dev_dbg(&tu->pt_dev->dev, "%s %p\n", __func__, gadget);
228
229	mutex_lock(&tu->serialize);
230
231	if (!gadget) {
232		if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
233			tahvo_usb_power_off(tu);
234		tu->phy.otg->gadget = NULL;
235		mutex_unlock(&tu->serialize);
236		return 0;
237	}
238
239	tu->phy.otg->gadget = gadget;
240	if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
241		tahvo_usb_become_peripheral(tu);
242
243	mutex_unlock(&tu->serialize);
244
245	return 0;
246}
247
248static irqreturn_t tahvo_usb_vbus_interrupt(int irq, void *_tu)
249{
250	struct tahvo_usb *tu = _tu;
251
252	mutex_lock(&tu->serialize);
253	check_vbus_state(tu);
254	mutex_unlock(&tu->serialize);
255
256	return IRQ_HANDLED;
257}
258
259static ssize_t otg_mode_show(struct device *device,
260			     struct device_attribute *attr, char *buf)
261{
262	struct tahvo_usb *tu = dev_get_drvdata(device);
263
264	switch (tu->tahvo_mode) {
265	case TAHVO_MODE_HOST:
266		return sprintf(buf, "host\n");
267	case TAHVO_MODE_PERIPHERAL:
268		return sprintf(buf, "peripheral\n");
269	}
270
271	return -EINVAL;
272}
273
274static ssize_t otg_mode_store(struct device *device,
275			      struct device_attribute *attr,
276			      const char *buf, size_t count)
277{
278	struct tahvo_usb *tu = dev_get_drvdata(device);
279	int r;
280
281	mutex_lock(&tu->serialize);
282	if (count >= 4 && strncmp(buf, "host", 4) == 0) {
283		if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
284			tahvo_usb_stop_peripheral(tu);
285		tu->tahvo_mode = TAHVO_MODE_HOST;
286		if (tu->phy.otg->host) {
287			dev_info(device, "HOST mode: host controller present\n");
288			tahvo_usb_become_host(tu);
289		} else {
290			dev_info(device, "HOST mode: no host controller, powering off\n");
291			tahvo_usb_power_off(tu);
292		}
293		r = strlen(buf);
294	} else if (count >= 10 && strncmp(buf, "peripheral", 10) == 0) {
295		if (tu->tahvo_mode == TAHVO_MODE_HOST)
296			tahvo_usb_stop_host(tu);
297		tu->tahvo_mode = TAHVO_MODE_PERIPHERAL;
298		if (tu->phy.otg->gadget) {
299			dev_info(device, "PERIPHERAL mode: gadget driver present\n");
300			tahvo_usb_become_peripheral(tu);
301		} else {
302			dev_info(device, "PERIPHERAL mode: no gadget driver, powering off\n");
303			tahvo_usb_power_off(tu);
304		}
305		r = strlen(buf);
306	} else {
307		r = -EINVAL;
308	}
309	mutex_unlock(&tu->serialize);
310
311	return r;
312}
313static DEVICE_ATTR_RW(otg_mode);
314
315static struct attribute *tahvo_attributes[] = {
316	&dev_attr_vbus.attr,
317	&dev_attr_otg_mode.attr,
318	NULL
319};
320
321static const struct attribute_group tahvo_attr_group = {
322	.attrs = tahvo_attributes,
323};
324
325static int tahvo_usb_probe(struct platform_device *pdev)
326{
327	struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent);
328	struct tahvo_usb *tu;
329	int ret;
330
331	tu = devm_kzalloc(&pdev->dev, sizeof(*tu), GFP_KERNEL);
332	if (!tu)
333		return -ENOMEM;
334
335	tu->phy.otg = devm_kzalloc(&pdev->dev, sizeof(*tu->phy.otg),
336				   GFP_KERNEL);
337	if (!tu->phy.otg)
338		return -ENOMEM;
339
340	tu->pt_dev = pdev;
341
342	/* Default mode */
343#ifdef CONFIG_TAHVO_USB_HOST_BY_DEFAULT
344	tu->tahvo_mode = TAHVO_MODE_HOST;
345#else
346	tu->tahvo_mode = TAHVO_MODE_PERIPHERAL;
347#endif
348
349	mutex_init(&tu->serialize);
350
351	tu->ick = devm_clk_get(&pdev->dev, "usb_l4_ick");
352	if (!IS_ERR(tu->ick))
353		clk_enable(tu->ick);
354
355	/*
356	 * Set initial state, so that we generate kevents only on state changes.
357	 */
358	tu->vbus_state = retu_read(rdev, TAHVO_REG_IDSR) & TAHVO_STAT_VBUS;
359
360	tu->extcon = devm_extcon_dev_allocate(&pdev->dev, tahvo_cable);
361	if (IS_ERR(tu->extcon)) {
362		dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
363		ret = PTR_ERR(tu->extcon);
364		goto err_disable_clk;
365	}
366
367	ret = devm_extcon_dev_register(&pdev->dev, tu->extcon);
368	if (ret) {
369		dev_err(&pdev->dev, "could not register extcon device: %d\n",
370			ret);
371		goto err_disable_clk;
372	}
373
374	/* Set the initial cable state. */
375	extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST,
376			       tu->tahvo_mode == TAHVO_MODE_HOST);
377	extcon_set_state_sync(tu->extcon, EXTCON_USB, tu->vbus_state);
378
379	/* Create OTG interface */
380	tahvo_usb_power_off(tu);
381	tu->phy.dev = &pdev->dev;
382	tu->phy.otg->state = OTG_STATE_UNDEFINED;
383	tu->phy.label = DRIVER_NAME;
384	tu->phy.set_suspend = tahvo_usb_set_suspend;
385
386	tu->phy.otg->usb_phy = &tu->phy;
387	tu->phy.otg->set_host = tahvo_usb_set_host;
388	tu->phy.otg->set_peripheral = tahvo_usb_set_peripheral;
389
390	ret = usb_add_phy(&tu->phy, USB_PHY_TYPE_USB2);
391	if (ret < 0) {
392		dev_err(&pdev->dev, "cannot register USB transceiver: %d\n",
393			ret);
394		goto err_disable_clk;
395	}
396
397	dev_set_drvdata(&pdev->dev, tu);
398
399	tu->irq = platform_get_irq(pdev, 0);
400	ret = request_threaded_irq(tu->irq, NULL, tahvo_usb_vbus_interrupt,
401				   IRQF_ONESHOT,
402				   "tahvo-vbus", tu);
403	if (ret) {
404		dev_err(&pdev->dev, "could not register tahvo-vbus irq: %d\n",
405			ret);
406		goto err_remove_phy;
407	}
408
409	/* Attributes */
410	ret = sysfs_create_group(&pdev->dev.kobj, &tahvo_attr_group);
411	if (ret) {
412		dev_err(&pdev->dev, "cannot create sysfs group: %d\n", ret);
413		goto err_free_irq;
414	}
415
416	return 0;
417
418err_free_irq:
419	free_irq(tu->irq, tu);
420err_remove_phy:
421	usb_remove_phy(&tu->phy);
422err_disable_clk:
423	if (!IS_ERR(tu->ick))
424		clk_disable(tu->ick);
425
426	return ret;
427}
428
429static int tahvo_usb_remove(struct platform_device *pdev)
430{
431	struct tahvo_usb *tu = platform_get_drvdata(pdev);
432
433	sysfs_remove_group(&pdev->dev.kobj, &tahvo_attr_group);
434	free_irq(tu->irq, tu);
435	usb_remove_phy(&tu->phy);
436	if (!IS_ERR(tu->ick))
437		clk_disable(tu->ick);
438
439	return 0;
440}
441
442static struct platform_driver tahvo_usb_driver = {
443	.probe		= tahvo_usb_probe,
444	.remove		= tahvo_usb_remove,
445	.driver		= {
446		.name	= "tahvo-usb",
447	},
448};
449module_platform_driver(tahvo_usb_driver);
450
451MODULE_DESCRIPTION("Tahvo USB transceiver driver");
452MODULE_LICENSE("GPL");
453MODULE_AUTHOR("Juha Yrjölä, Tony Lindgren, and Timo Teräs");
454MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");