phy-bcm-ns2-usbdrd.c (11444B)
1/* 2 * Copyright (C) 2017 Broadcom 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License as 6 * published by the Free Software Foundation version 2. 7 * 8 * This program is distributed "as is" WITHOUT ANY WARRANTY of any 9 * kind, whether express or implied; without even the implied warranty 10 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 */ 13 14#include <linux/delay.h> 15#include <linux/extcon-provider.h> 16#include <linux/gpio.h> 17#include <linux/gpio/consumer.h> 18#include <linux/init.h> 19#include <linux/interrupt.h> 20#include <linux/io.h> 21#include <linux/iopoll.h> 22#include <linux/irq.h> 23#include <linux/mfd/syscon.h> 24#include <linux/module.h> 25#include <linux/of.h> 26#include <linux/of_address.h> 27#include <linux/phy/phy.h> 28#include <linux/platform_device.h> 29#include <linux/regmap.h> 30#include <linux/slab.h> 31#include <linux/workqueue.h> 32 33#define ICFG_DRD_AFE 0x0 34#define ICFG_MISC_STAT 0x18 35#define ICFG_DRD_P0CTL 0x1C 36#define ICFG_STRAP_CTRL 0x20 37#define ICFG_FSM_CTRL 0x24 38 39#define ICFG_DEV_BIT BIT(2) 40#define IDM_RST_BIT BIT(0) 41#define AFE_CORERDY_VDDC BIT(18) 42#define PHY_PLL_RESETB BIT(15) 43#define PHY_RESETB BIT(14) 44#define PHY_PLL_LOCK BIT(0) 45 46#define DRD_DEV_MODE BIT(20) 47#define OHCI_OVRCUR_POL BIT(11) 48#define ICFG_OFF_MODE BIT(6) 49#define PLL_LOCK_RETRY 1000 50 51#define EVT_DEVICE 0 52#define EVT_HOST 1 53 54#define DRD_HOST_MODE (BIT(2) | BIT(3)) 55#define DRD_DEVICE_MODE (BIT(4) | BIT(5)) 56#define DRD_HOST_VAL 0x803 57#define DRD_DEV_VAL 0x807 58#define GPIO_DELAY 20 59 60struct ns2_phy_data; 61struct ns2_phy_driver { 62 void __iomem *icfgdrd_regs; 63 void __iomem *idmdrd_rst_ctrl; 64 void __iomem *crmu_usb2_ctrl; 65 void __iomem *usb2h_strap_reg; 66 struct ns2_phy_data *data; 67 struct extcon_dev *edev; 68 struct gpio_desc *vbus_gpiod; 69 struct gpio_desc *id_gpiod; 70 int id_irq; 71 int vbus_irq; 72 unsigned long debounce_jiffies; 73 struct delayed_work wq_extcon; 74}; 75 76struct ns2_phy_data { 77 struct ns2_phy_driver *driver; 78 struct phy *phy; 79 int new_state; 80}; 81 82static const unsigned int usb_extcon_cable[] = { 83 EXTCON_USB, 84 EXTCON_USB_HOST, 85 EXTCON_NONE, 86}; 87 88static inline int pll_lock_stat(u32 usb_reg, int reg_mask, 89 struct ns2_phy_driver *driver) 90{ 91 u32 val; 92 93 return readl_poll_timeout_atomic(driver->icfgdrd_regs + usb_reg, 94 val, (val & reg_mask), 1, 95 PLL_LOCK_RETRY); 96} 97 98static int ns2_drd_phy_init(struct phy *phy) 99{ 100 struct ns2_phy_data *data = phy_get_drvdata(phy); 101 struct ns2_phy_driver *driver = data->driver; 102 u32 val; 103 104 val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL); 105 106 if (data->new_state == EVT_HOST) { 107 val &= ~DRD_DEVICE_MODE; 108 val |= DRD_HOST_MODE; 109 } else { 110 val &= ~DRD_HOST_MODE; 111 val |= DRD_DEVICE_MODE; 112 } 113 writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); 114 115 return 0; 116} 117 118static int ns2_drd_phy_poweroff(struct phy *phy) 119{ 120 struct ns2_phy_data *data = phy_get_drvdata(phy); 121 struct ns2_phy_driver *driver = data->driver; 122 u32 val; 123 124 val = readl(driver->crmu_usb2_ctrl); 125 val &= ~AFE_CORERDY_VDDC; 126 writel(val, driver->crmu_usb2_ctrl); 127 128 val = readl(driver->crmu_usb2_ctrl); 129 val &= ~DRD_DEV_MODE; 130 writel(val, driver->crmu_usb2_ctrl); 131 132 /* Disable Host and Device Mode */ 133 val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL); 134 val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE | ICFG_OFF_MODE); 135 writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); 136 137 return 0; 138} 139 140static int ns2_drd_phy_poweron(struct phy *phy) 141{ 142 struct ns2_phy_data *data = phy_get_drvdata(phy); 143 struct ns2_phy_driver *driver = data->driver; 144 u32 extcon_event = data->new_state; 145 int ret; 146 u32 val; 147 148 if (extcon_event == EVT_DEVICE) { 149 writel(DRD_DEV_VAL, driver->icfgdrd_regs + ICFG_DRD_P0CTL); 150 151 val = readl(driver->idmdrd_rst_ctrl); 152 val &= ~IDM_RST_BIT; 153 writel(val, driver->idmdrd_rst_ctrl); 154 155 val = readl(driver->crmu_usb2_ctrl); 156 val |= (AFE_CORERDY_VDDC | DRD_DEV_MODE); 157 writel(val, driver->crmu_usb2_ctrl); 158 159 /* Bring PHY and PHY_PLL out of Reset */ 160 val = readl(driver->crmu_usb2_ctrl); 161 val |= (PHY_PLL_RESETB | PHY_RESETB); 162 writel(val, driver->crmu_usb2_ctrl); 163 164 ret = pll_lock_stat(ICFG_MISC_STAT, PHY_PLL_LOCK, driver); 165 if (ret < 0) { 166 dev_err(&phy->dev, "Phy PLL lock failed\n"); 167 return ret; 168 } 169 } else { 170 writel(DRD_HOST_VAL, driver->icfgdrd_regs + ICFG_DRD_P0CTL); 171 172 val = readl(driver->crmu_usb2_ctrl); 173 val |= AFE_CORERDY_VDDC; 174 writel(val, driver->crmu_usb2_ctrl); 175 176 ret = pll_lock_stat(ICFG_MISC_STAT, PHY_PLL_LOCK, driver); 177 if (ret < 0) { 178 dev_err(&phy->dev, "Phy PLL lock failed\n"); 179 return ret; 180 } 181 182 val = readl(driver->idmdrd_rst_ctrl); 183 val &= ~IDM_RST_BIT; 184 writel(val, driver->idmdrd_rst_ctrl); 185 186 /* port over current Polarity */ 187 val = readl(driver->usb2h_strap_reg); 188 val |= OHCI_OVRCUR_POL; 189 writel(val, driver->usb2h_strap_reg); 190 } 191 192 return 0; 193} 194 195static void connect_change(struct ns2_phy_driver *driver) 196{ 197 u32 extcon_event; 198 u32 val; 199 200 extcon_event = driver->data->new_state; 201 val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL); 202 203 switch (extcon_event) { 204 case EVT_DEVICE: 205 val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE); 206 writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); 207 208 val = (val & ~DRD_HOST_MODE) | DRD_DEVICE_MODE; 209 writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); 210 211 val = readl(driver->icfgdrd_regs + ICFG_DRD_P0CTL); 212 val |= ICFG_DEV_BIT; 213 writel(val, driver->icfgdrd_regs + ICFG_DRD_P0CTL); 214 break; 215 216 case EVT_HOST: 217 val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE); 218 writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); 219 220 val = (val & ~DRD_DEVICE_MODE) | DRD_HOST_MODE; 221 writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); 222 223 val = readl(driver->usb2h_strap_reg); 224 val |= OHCI_OVRCUR_POL; 225 writel(val, driver->usb2h_strap_reg); 226 227 val = readl(driver->icfgdrd_regs + ICFG_DRD_P0CTL); 228 val &= ~ICFG_DEV_BIT; 229 writel(val, driver->icfgdrd_regs + ICFG_DRD_P0CTL); 230 break; 231 232 default: 233 pr_err("Invalid extcon event\n"); 234 break; 235 } 236} 237 238static void extcon_work(struct work_struct *work) 239{ 240 struct ns2_phy_driver *driver; 241 int vbus; 242 int id; 243 244 driver = container_of(to_delayed_work(work), 245 struct ns2_phy_driver, wq_extcon); 246 247 id = gpiod_get_value_cansleep(driver->id_gpiod); 248 vbus = gpiod_get_value_cansleep(driver->vbus_gpiod); 249 250 if (!id && vbus) { /* Host connected */ 251 extcon_set_state_sync(driver->edev, EXTCON_USB_HOST, true); 252 pr_debug("Host cable connected\n"); 253 driver->data->new_state = EVT_HOST; 254 connect_change(driver); 255 } else if (id && !vbus) { /* Disconnected */ 256 extcon_set_state_sync(driver->edev, EXTCON_USB_HOST, false); 257 extcon_set_state_sync(driver->edev, EXTCON_USB, false); 258 pr_debug("Cable disconnected\n"); 259 } else if (id && vbus) { /* Device connected */ 260 extcon_set_state_sync(driver->edev, EXTCON_USB, true); 261 pr_debug("Device cable connected\n"); 262 driver->data->new_state = EVT_DEVICE; 263 connect_change(driver); 264 } 265} 266 267static irqreturn_t gpio_irq_handler(int irq, void *dev_id) 268{ 269 struct ns2_phy_driver *driver = dev_id; 270 271 queue_delayed_work(system_power_efficient_wq, &driver->wq_extcon, 272 driver->debounce_jiffies); 273 274 return IRQ_HANDLED; 275} 276 277static const struct phy_ops ops = { 278 .init = ns2_drd_phy_init, 279 .power_on = ns2_drd_phy_poweron, 280 .power_off = ns2_drd_phy_poweroff, 281 .owner = THIS_MODULE, 282}; 283 284static const struct of_device_id ns2_drd_phy_dt_ids[] = { 285 { .compatible = "brcm,ns2-drd-phy", }, 286 { } 287}; 288MODULE_DEVICE_TABLE(of, ns2_drd_phy_dt_ids); 289 290static int ns2_drd_phy_probe(struct platform_device *pdev) 291{ 292 struct phy_provider *phy_provider; 293 struct device *dev = &pdev->dev; 294 struct ns2_phy_driver *driver; 295 struct ns2_phy_data *data; 296 int ret; 297 u32 val; 298 299 driver = devm_kzalloc(dev, sizeof(struct ns2_phy_driver), 300 GFP_KERNEL); 301 if (!driver) 302 return -ENOMEM; 303 304 driver->data = devm_kzalloc(dev, sizeof(struct ns2_phy_data), 305 GFP_KERNEL); 306 if (!driver->data) 307 return -ENOMEM; 308 309 driver->icfgdrd_regs = devm_platform_ioremap_resource_byname(pdev, "icfg"); 310 if (IS_ERR(driver->icfgdrd_regs)) 311 return PTR_ERR(driver->icfgdrd_regs); 312 313 driver->idmdrd_rst_ctrl = devm_platform_ioremap_resource_byname(pdev, "rst-ctrl"); 314 if (IS_ERR(driver->idmdrd_rst_ctrl)) 315 return PTR_ERR(driver->idmdrd_rst_ctrl); 316 317 driver->crmu_usb2_ctrl = devm_platform_ioremap_resource_byname(pdev, "crmu-ctrl"); 318 if (IS_ERR(driver->crmu_usb2_ctrl)) 319 return PTR_ERR(driver->crmu_usb2_ctrl); 320 321 driver->usb2h_strap_reg = devm_platform_ioremap_resource_byname(pdev, "usb2-strap"); 322 if (IS_ERR(driver->usb2h_strap_reg)) 323 return PTR_ERR(driver->usb2h_strap_reg); 324 325 /* create extcon */ 326 driver->id_gpiod = devm_gpiod_get(&pdev->dev, "id", GPIOD_IN); 327 if (IS_ERR(driver->id_gpiod)) { 328 dev_err(dev, "failed to get ID GPIO\n"); 329 return PTR_ERR(driver->id_gpiod); 330 } 331 driver->vbus_gpiod = devm_gpiod_get(&pdev->dev, "vbus", GPIOD_IN); 332 if (IS_ERR(driver->vbus_gpiod)) { 333 dev_err(dev, "failed to get VBUS GPIO\n"); 334 return PTR_ERR(driver->vbus_gpiod); 335 } 336 337 driver->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); 338 if (IS_ERR(driver->edev)) { 339 dev_err(dev, "failed to allocate extcon device\n"); 340 return -ENOMEM; 341 } 342 343 ret = devm_extcon_dev_register(dev, driver->edev); 344 if (ret < 0) { 345 dev_err(dev, "failed to register extcon device\n"); 346 return ret; 347 } 348 349 ret = gpiod_set_debounce(driver->id_gpiod, GPIO_DELAY * 1000); 350 if (ret < 0) 351 driver->debounce_jiffies = msecs_to_jiffies(GPIO_DELAY); 352 353 INIT_DELAYED_WORK(&driver->wq_extcon, extcon_work); 354 355 driver->id_irq = gpiod_to_irq(driver->id_gpiod); 356 if (driver->id_irq < 0) { 357 dev_err(dev, "failed to get ID IRQ\n"); 358 return driver->id_irq; 359 } 360 361 driver->vbus_irq = gpiod_to_irq(driver->vbus_gpiod); 362 if (driver->vbus_irq < 0) { 363 dev_err(dev, "failed to get ID IRQ\n"); 364 return driver->vbus_irq; 365 } 366 367 ret = devm_request_irq(dev, driver->id_irq, gpio_irq_handler, 368 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 369 "usb_id", driver); 370 if (ret < 0) { 371 dev_err(dev, "failed to request handler for ID IRQ\n"); 372 return ret; 373 } 374 375 ret = devm_request_irq(dev, driver->vbus_irq, gpio_irq_handler, 376 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 377 "usb_vbus", driver); 378 if (ret < 0) { 379 dev_err(dev, "failed to request handler for VBUS IRQ\n"); 380 return ret; 381 } 382 383 dev_set_drvdata(dev, driver); 384 385 /* Shutdown all ports. They can be powered up as required */ 386 val = readl(driver->crmu_usb2_ctrl); 387 val &= ~(AFE_CORERDY_VDDC | PHY_RESETB); 388 writel(val, driver->crmu_usb2_ctrl); 389 390 data = driver->data; 391 data->phy = devm_phy_create(dev, dev->of_node, &ops); 392 if (IS_ERR(data->phy)) { 393 dev_err(dev, "Failed to create usb drd phy\n"); 394 return PTR_ERR(data->phy); 395 } 396 397 data->driver = driver; 398 phy_set_drvdata(data->phy, data); 399 400 phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); 401 if (IS_ERR(phy_provider)) { 402 dev_err(dev, "Failed to register as phy provider\n"); 403 return PTR_ERR(phy_provider); 404 } 405 406 platform_set_drvdata(pdev, driver); 407 408 dev_info(dev, "Registered NS2 DRD Phy device\n"); 409 queue_delayed_work(system_power_efficient_wq, &driver->wq_extcon, 410 driver->debounce_jiffies); 411 412 return 0; 413} 414 415static struct platform_driver ns2_drd_phy_driver = { 416 .probe = ns2_drd_phy_probe, 417 .driver = { 418 .name = "bcm-ns2-usbphy", 419 .of_match_table = of_match_ptr(ns2_drd_phy_dt_ids), 420 }, 421}; 422module_platform_driver(ns2_drd_phy_driver); 423 424MODULE_ALIAS("platform:bcm-ns2-drd-phy"); 425MODULE_AUTHOR("Broadcom"); 426MODULE_DESCRIPTION("Broadcom NS2 USB2 PHY driver"); 427MODULE_LICENSE("GPL v2");