Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Tahvo USB transceiver driver
0004  *
0005  * Copyright (C) 2005-2006 Nokia Corporation
0006  *
0007  * Parts copied from isp1301_omap.c.
0008  * Copyright (C) 2004 Texas Instruments
0009  * Copyright (C) 2004 David Brownell
0010  *
0011  * Original driver written by Juha Yrjölä, Tony Lindgren and Timo Teräs.
0012  * Modified for Retu/Tahvo MFD by Aaro Koskinen.
0013  */
0014 
0015 #include <linux/io.h>
0016 #include <linux/clk.h>
0017 #include <linux/usb.h>
0018 #include <linux/extcon-provider.h>
0019 #include <linux/kernel.h>
0020 #include <linux/module.h>
0021 #include <linux/usb/otg.h>
0022 #include <linux/mfd/retu.h>
0023 #include <linux/usb/gadget.h>
0024 #include <linux/platform_device.h>
0025 
0026 #define DRIVER_NAME     "tahvo-usb"
0027 
0028 #define TAHVO_REG_IDSR  0x02
0029 #define TAHVO_REG_USBR  0x06
0030 
0031 #define USBR_SLAVE_CONTROL  (1 << 8)
0032 #define USBR_VPPVIO_SW      (1 << 7)
0033 #define USBR_SPEED      (1 << 6)
0034 #define USBR_REGOUT     (1 << 5)
0035 #define USBR_MASTER_SW2     (1 << 4)
0036 #define USBR_MASTER_SW1     (1 << 3)
0037 #define USBR_SLAVE_SW       (1 << 2)
0038 #define USBR_NSUSPEND       (1 << 1)
0039 #define USBR_SEMODE     (1 << 0)
0040 
0041 #define TAHVO_MODE_HOST     0
0042 #define TAHVO_MODE_PERIPHERAL   1
0043 
0044 struct tahvo_usb {
0045     struct platform_device  *pt_dev;
0046     struct usb_phy      phy;
0047     int         vbus_state;
0048     struct mutex        serialize;
0049     struct clk      *ick;
0050     int         irq;
0051     int         tahvo_mode;
0052     struct extcon_dev   *extcon;
0053 };
0054 
0055 static const unsigned int tahvo_cable[] = {
0056     EXTCON_USB,
0057     EXTCON_USB_HOST,
0058 
0059     EXTCON_NONE,
0060 };
0061 
0062 static ssize_t vbus_show(struct device *device,
0063                    struct device_attribute *attr, char *buf)
0064 {
0065     struct tahvo_usb *tu = dev_get_drvdata(device);
0066     return sprintf(buf, "%s\n", tu->vbus_state ? "on" : "off");
0067 }
0068 static DEVICE_ATTR_RO(vbus);
0069 
0070 static void check_vbus_state(struct tahvo_usb *tu)
0071 {
0072     struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
0073     int reg, prev_state;
0074 
0075     reg = retu_read(rdev, TAHVO_REG_IDSR);
0076     if (reg & TAHVO_STAT_VBUS) {
0077         switch (tu->phy.otg->state) {
0078         case OTG_STATE_B_IDLE:
0079             /* Enable the gadget driver */
0080             if (tu->phy.otg->gadget)
0081                 usb_gadget_vbus_connect(tu->phy.otg->gadget);
0082             tu->phy.otg->state = OTG_STATE_B_PERIPHERAL;
0083             usb_phy_set_event(&tu->phy, USB_EVENT_ENUMERATED);
0084             break;
0085         case OTG_STATE_A_IDLE:
0086             /*
0087              * Session is now valid assuming the USB hub is driving
0088              * Vbus.
0089              */
0090             tu->phy.otg->state = OTG_STATE_A_HOST;
0091             break;
0092         default:
0093             break;
0094         }
0095         dev_info(&tu->pt_dev->dev, "USB cable connected\n");
0096     } else {
0097         switch (tu->phy.otg->state) {
0098         case OTG_STATE_B_PERIPHERAL:
0099             if (tu->phy.otg->gadget)
0100                 usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
0101             tu->phy.otg->state = OTG_STATE_B_IDLE;
0102             usb_phy_set_event(&tu->phy, USB_EVENT_NONE);
0103             break;
0104         case OTG_STATE_A_HOST:
0105             tu->phy.otg->state = OTG_STATE_A_IDLE;
0106             break;
0107         default:
0108             break;
0109         }
0110         dev_info(&tu->pt_dev->dev, "USB cable disconnected\n");
0111     }
0112 
0113     prev_state = tu->vbus_state;
0114     tu->vbus_state = reg & TAHVO_STAT_VBUS;
0115     if (prev_state != tu->vbus_state) {
0116         extcon_set_state_sync(tu->extcon, EXTCON_USB, tu->vbus_state);
0117         sysfs_notify(&tu->pt_dev->dev.kobj, NULL, "vbus_state");
0118     }
0119 }
0120 
0121 static void tahvo_usb_become_host(struct tahvo_usb *tu)
0122 {
0123     struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
0124 
0125     extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST, true);
0126 
0127     /* Power up the transceiver in USB host mode */
0128     retu_write(rdev, TAHVO_REG_USBR, USBR_REGOUT | USBR_NSUSPEND |
0129            USBR_MASTER_SW2 | USBR_MASTER_SW1);
0130     tu->phy.otg->state = OTG_STATE_A_IDLE;
0131 
0132     check_vbus_state(tu);
0133 }
0134 
0135 static void tahvo_usb_stop_host(struct tahvo_usb *tu)
0136 {
0137     tu->phy.otg->state = OTG_STATE_A_IDLE;
0138 }
0139 
0140 static void tahvo_usb_become_peripheral(struct tahvo_usb *tu)
0141 {
0142     struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
0143 
0144     extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST, false);
0145 
0146     /* Power up transceiver and set it in USB peripheral mode */
0147     retu_write(rdev, TAHVO_REG_USBR, USBR_SLAVE_CONTROL | USBR_REGOUT |
0148            USBR_NSUSPEND | USBR_SLAVE_SW);
0149     tu->phy.otg->state = OTG_STATE_B_IDLE;
0150 
0151     check_vbus_state(tu);
0152 }
0153 
0154 static void tahvo_usb_stop_peripheral(struct tahvo_usb *tu)
0155 {
0156     if (tu->phy.otg->gadget)
0157         usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
0158     tu->phy.otg->state = OTG_STATE_B_IDLE;
0159 }
0160 
0161 static void tahvo_usb_power_off(struct tahvo_usb *tu)
0162 {
0163     struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
0164 
0165     /* Disable gadget controller if any */
0166     if (tu->phy.otg->gadget)
0167         usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
0168 
0169     /* Power off transceiver */
0170     retu_write(rdev, TAHVO_REG_USBR, 0);
0171     tu->phy.otg->state = OTG_STATE_UNDEFINED;
0172 }
0173 
0174 static int tahvo_usb_set_suspend(struct usb_phy *dev, int suspend)
0175 {
0176     struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, phy);
0177     struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
0178     u16 w;
0179 
0180     dev_dbg(&tu->pt_dev->dev, "%s\n", __func__);
0181 
0182     w = retu_read(rdev, TAHVO_REG_USBR);
0183     if (suspend)
0184         w &= ~USBR_NSUSPEND;
0185     else
0186         w |= USBR_NSUSPEND;
0187     retu_write(rdev, TAHVO_REG_USBR, w);
0188 
0189     return 0;
0190 }
0191 
0192 static int tahvo_usb_set_host(struct usb_otg *otg, struct usb_bus *host)
0193 {
0194     struct tahvo_usb *tu = container_of(otg->usb_phy, struct tahvo_usb,
0195                         phy);
0196 
0197     mutex_lock(&tu->serialize);
0198 
0199     if (host == NULL) {
0200         if (tu->tahvo_mode == TAHVO_MODE_HOST)
0201             tahvo_usb_power_off(tu);
0202         otg->host = NULL;
0203         mutex_unlock(&tu->serialize);
0204         return 0;
0205     }
0206 
0207     if (tu->tahvo_mode == TAHVO_MODE_HOST) {
0208         otg->host = NULL;
0209         tahvo_usb_become_host(tu);
0210     }
0211 
0212     otg->host = host;
0213 
0214     mutex_unlock(&tu->serialize);
0215 
0216     return 0;
0217 }
0218 
0219 static int tahvo_usb_set_peripheral(struct usb_otg *otg,
0220                     struct usb_gadget *gadget)
0221 {
0222     struct tahvo_usb *tu = container_of(otg->usb_phy, struct tahvo_usb,
0223                         phy);
0224 
0225     mutex_lock(&tu->serialize);
0226 
0227     if (!gadget) {
0228         if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
0229             tahvo_usb_power_off(tu);
0230         tu->phy.otg->gadget = NULL;
0231         mutex_unlock(&tu->serialize);
0232         return 0;
0233     }
0234 
0235     tu->phy.otg->gadget = gadget;
0236     if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
0237         tahvo_usb_become_peripheral(tu);
0238 
0239     mutex_unlock(&tu->serialize);
0240 
0241     return 0;
0242 }
0243 
0244 static irqreturn_t tahvo_usb_vbus_interrupt(int irq, void *_tu)
0245 {
0246     struct tahvo_usb *tu = _tu;
0247 
0248     mutex_lock(&tu->serialize);
0249     check_vbus_state(tu);
0250     mutex_unlock(&tu->serialize);
0251 
0252     return IRQ_HANDLED;
0253 }
0254 
0255 static ssize_t otg_mode_show(struct device *device,
0256                  struct device_attribute *attr, char *buf)
0257 {
0258     struct tahvo_usb *tu = dev_get_drvdata(device);
0259 
0260     switch (tu->tahvo_mode) {
0261     case TAHVO_MODE_HOST:
0262         return sprintf(buf, "host\n");
0263     case TAHVO_MODE_PERIPHERAL:
0264         return sprintf(buf, "peripheral\n");
0265     }
0266 
0267     return -EINVAL;
0268 }
0269 
0270 static ssize_t otg_mode_store(struct device *device,
0271                   struct device_attribute *attr,
0272                   const char *buf, size_t count)
0273 {
0274     struct tahvo_usb *tu = dev_get_drvdata(device);
0275     int r;
0276 
0277     mutex_lock(&tu->serialize);
0278     if (count >= 4 && strncmp(buf, "host", 4) == 0) {
0279         if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
0280             tahvo_usb_stop_peripheral(tu);
0281         tu->tahvo_mode = TAHVO_MODE_HOST;
0282         if (tu->phy.otg->host) {
0283             dev_info(device, "HOST mode: host controller present\n");
0284             tahvo_usb_become_host(tu);
0285         } else {
0286             dev_info(device, "HOST mode: no host controller, powering off\n");
0287             tahvo_usb_power_off(tu);
0288         }
0289         r = strlen(buf);
0290     } else if (count >= 10 && strncmp(buf, "peripheral", 10) == 0) {
0291         if (tu->tahvo_mode == TAHVO_MODE_HOST)
0292             tahvo_usb_stop_host(tu);
0293         tu->tahvo_mode = TAHVO_MODE_PERIPHERAL;
0294         if (tu->phy.otg->gadget) {
0295             dev_info(device, "PERIPHERAL mode: gadget driver present\n");
0296             tahvo_usb_become_peripheral(tu);
0297         } else {
0298             dev_info(device, "PERIPHERAL mode: no gadget driver, powering off\n");
0299             tahvo_usb_power_off(tu);
0300         }
0301         r = strlen(buf);
0302     } else {
0303         r = -EINVAL;
0304     }
0305     mutex_unlock(&tu->serialize);
0306 
0307     return r;
0308 }
0309 static DEVICE_ATTR_RW(otg_mode);
0310 
0311 static struct attribute *tahvo_attrs[] = {
0312     &dev_attr_vbus.attr,
0313     &dev_attr_otg_mode.attr,
0314     NULL
0315 };
0316 ATTRIBUTE_GROUPS(tahvo);
0317 
0318 static int tahvo_usb_probe(struct platform_device *pdev)
0319 {
0320     struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent);
0321     struct tahvo_usb *tu;
0322     int ret;
0323 
0324     tu = devm_kzalloc(&pdev->dev, sizeof(*tu), GFP_KERNEL);
0325     if (!tu)
0326         return -ENOMEM;
0327 
0328     tu->phy.otg = devm_kzalloc(&pdev->dev, sizeof(*tu->phy.otg),
0329                    GFP_KERNEL);
0330     if (!tu->phy.otg)
0331         return -ENOMEM;
0332 
0333     tu->pt_dev = pdev;
0334 
0335     /* Default mode */
0336 #ifdef CONFIG_TAHVO_USB_HOST_BY_DEFAULT
0337     tu->tahvo_mode = TAHVO_MODE_HOST;
0338 #else
0339     tu->tahvo_mode = TAHVO_MODE_PERIPHERAL;
0340 #endif
0341 
0342     mutex_init(&tu->serialize);
0343 
0344     tu->ick = devm_clk_get(&pdev->dev, "usb_l4_ick");
0345     if (!IS_ERR(tu->ick))
0346         clk_enable(tu->ick);
0347 
0348     /*
0349      * Set initial state, so that we generate kevents only on state changes.
0350      */
0351     tu->vbus_state = retu_read(rdev, TAHVO_REG_IDSR) & TAHVO_STAT_VBUS;
0352 
0353     tu->extcon = devm_extcon_dev_allocate(&pdev->dev, tahvo_cable);
0354     if (IS_ERR(tu->extcon)) {
0355         dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
0356         ret = PTR_ERR(tu->extcon);
0357         goto err_disable_clk;
0358     }
0359 
0360     ret = devm_extcon_dev_register(&pdev->dev, tu->extcon);
0361     if (ret) {
0362         dev_err(&pdev->dev, "could not register extcon device: %d\n",
0363             ret);
0364         goto err_disable_clk;
0365     }
0366 
0367     /* Set the initial cable state. */
0368     extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST,
0369                    tu->tahvo_mode == TAHVO_MODE_HOST);
0370     extcon_set_state_sync(tu->extcon, EXTCON_USB, tu->vbus_state);
0371 
0372     /* Create OTG interface */
0373     tahvo_usb_power_off(tu);
0374     tu->phy.dev = &pdev->dev;
0375     tu->phy.otg->state = OTG_STATE_UNDEFINED;
0376     tu->phy.label = DRIVER_NAME;
0377     tu->phy.set_suspend = tahvo_usb_set_suspend;
0378 
0379     tu->phy.otg->usb_phy = &tu->phy;
0380     tu->phy.otg->set_host = tahvo_usb_set_host;
0381     tu->phy.otg->set_peripheral = tahvo_usb_set_peripheral;
0382 
0383     ret = usb_add_phy(&tu->phy, USB_PHY_TYPE_USB2);
0384     if (ret < 0) {
0385         dev_err(&pdev->dev, "cannot register USB transceiver: %d\n",
0386             ret);
0387         goto err_disable_clk;
0388     }
0389 
0390     dev_set_drvdata(&pdev->dev, tu);
0391 
0392     tu->irq = ret = platform_get_irq(pdev, 0);
0393     if (ret < 0)
0394         return ret;
0395     ret = request_threaded_irq(tu->irq, NULL, tahvo_usb_vbus_interrupt,
0396                    IRQF_ONESHOT,
0397                    "tahvo-vbus", tu);
0398     if (ret) {
0399         dev_err(&pdev->dev, "could not register tahvo-vbus irq: %d\n",
0400             ret);
0401         goto err_remove_phy;
0402     }
0403 
0404     return 0;
0405 
0406 err_remove_phy:
0407     usb_remove_phy(&tu->phy);
0408 err_disable_clk:
0409     if (!IS_ERR(tu->ick))
0410         clk_disable(tu->ick);
0411 
0412     return ret;
0413 }
0414 
0415 static int tahvo_usb_remove(struct platform_device *pdev)
0416 {
0417     struct tahvo_usb *tu = platform_get_drvdata(pdev);
0418 
0419     free_irq(tu->irq, tu);
0420     usb_remove_phy(&tu->phy);
0421     if (!IS_ERR(tu->ick))
0422         clk_disable(tu->ick);
0423 
0424     return 0;
0425 }
0426 
0427 static struct platform_driver tahvo_usb_driver = {
0428     .probe      = tahvo_usb_probe,
0429     .remove     = tahvo_usb_remove,
0430     .driver     = {
0431         .name   = "tahvo-usb",
0432         .dev_groups = tahvo_groups,
0433     },
0434 };
0435 module_platform_driver(tahvo_usb_driver);
0436 
0437 MODULE_DESCRIPTION("Tahvo USB transceiver driver");
0438 MODULE_LICENSE("GPL");
0439 MODULE_AUTHOR("Juha Yrjölä, Tony Lindgren, and Timo Teräs");
0440 MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");