Skip to content
Navigation Menu
Toggle navigation
Sign in
In this repository
All GitHub Enterprise
↵
Jump to
↵
No suggested jump to results
In this repository
All GitHub Enterprise
↵
Jump to
↵
In this organization
All GitHub Enterprise
↵
Jump to
↵
In this repository
All GitHub Enterprise
↵
Jump to
↵
Sign in
Reseting focus
You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
Dismiss alert
{{ message }}
mariux64
/
linux
Public
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Issues
2
Pull requests
0
Actions
Projects
0
Wiki
Security
Insights
Additional navigation options
Code
Issues
Pull requests
Actions
Projects
Wiki
Security
Insights
Files
c46ac50
Documentation
LICENSES
arch
block
certs
crypto
drivers
accel
accessibility
acpi
amba
android
ata
atm
auxdisplay
base
bcma
block
bluetooth
bus
cache
cdrom
cdx
char
clk
clocksource
comedi
connector
counter
cpufreq
cpuidle
crypto
cxl
dax
dca
devfreq
dio
dma-buf
dma
dpll
edac
eisa
extcon
firewire
firmware
fpga
fsi
gnss
gpio
gpu
greybus
hid
hsi
hte
hv
hwmon
hwspinlock
hwtracing
i2c
i3c
idle
iio
infiniband
input
interconnect
iommu
ipack
irqchip
isdn
leds
macintosh
mailbox
mcb
md
media
memory
memstick
message
mfd
misc
mmc
most
mtd
mux
net
nfc
ntb
nubus
nvdimm
nvme
nvmem
of
opp
parisc
parport
pci
pcmcia
peci
perf
phy
pinctrl
platform
pmdomain
pnp
power
powercap
pps
ps3
ptp
pwm
rapidio
ras
regulator
remoteproc
reset
rpmsg
rtc
s390
sbus
scsi
sh
siox
slimbus
soc
soundwire
spi
spmi
ssb
staging
target
tc
tee
thermal
thunderbolt
tty
ufs
uio
usb
atm
c67x00
cdns3
Kconfig
Makefile
cdns3-debug.h
cdns3-ep0.c
cdns3-gadget.c
cdns3-gadget.h
cdns3-imx.c
cdns3-pci-wrap.c
cdns3-plat.c
cdns3-starfive.c
cdns3-ti.c
cdns3-trace.c
cdns3-trace.h
cdnsp-debug.h
cdnsp-ep0.c
cdnsp-gadget.c
cdnsp-gadget.h
cdnsp-mem.c
cdnsp-pci.c
cdnsp-ring.c
cdnsp-trace.c
cdnsp-trace.h
core.c
core.h
drd.c
drd.h
gadget-export.h
host-export.h
host.c
chipidea
class
common
core
dwc2
dwc3
early
fotg210
gadget
host
image
isp1760
misc
mon
mtu3
musb
phy
renesas_usbhs
roles
serial
storage
typec
usbip
Kconfig
Makefile
usb-skeleton.c
vdpa
vfio
vhost
video
virt
virtio
w1
watchdog
xen
zorro
Kconfig
Makefile
fs
include
init
io_uring
ipc
kernel
lib
mm
net
rust
samples
scripts
security
sound
tools
usr
virt
.clang-format
.cocciconfig
.editorconfig
.get_maintainer.ignore
.gitattributes
.gitignore
.mailmap
.rustfmt.toml
COPYING
CREDITS
Kbuild
Kconfig
MAINTAINERS
Makefile
README
Breadcrumbs
linux
/
drivers
/
usb
/
cdns3
/
core.c
Blame
Blame
Latest commit
History
History
581 lines (484 loc) · 12.3 KB
Breadcrumbs
linux
/
drivers
/
usb
/
cdns3
/
core.c
Top
File metadata and controls
Code
Blame
581 lines (484 loc) · 12.3 KB
Raw
// SPDX-License-Identifier: GPL-2.0 /* * Cadence USBSS and USBSSP DRD Driver. * * Copyright (C) 2018-2019 Cadence. * Copyright (C) 2017-2018 NXP * Copyright (C) 2019 Texas Instruments * * Author: Peter Chen <peter.chen@nxp.com> * Pawel Laszczak <pawell@cadence.com> * Roger Quadros <rogerq@ti.com> */ #include <linux/dma-mapping.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/pm_runtime.h> #include "core.h" #include "host-export.h" #include "drd.h" static int cdns_idle_init(struct cdns *cdns); static int cdns_role_start(struct cdns *cdns, enum usb_role role) { int ret; if (WARN_ON(role > USB_ROLE_DEVICE)) return 0; mutex_lock(&cdns->mutex); cdns->role = role; mutex_unlock(&cdns->mutex); if (!cdns->roles[role]) return -ENXIO; if (cdns->roles[role]->state == CDNS_ROLE_STATE_ACTIVE) return 0; mutex_lock(&cdns->mutex); ret = cdns->roles[role]->start(cdns); if (!ret) cdns->roles[role]->state = CDNS_ROLE_STATE_ACTIVE; mutex_unlock(&cdns->mutex); return ret; } static void cdns_role_stop(struct cdns *cdns) { enum usb_role role = cdns->role; if (WARN_ON(role > USB_ROLE_DEVICE)) return; if (cdns->roles[role]->state == CDNS_ROLE_STATE_INACTIVE) return; mutex_lock(&cdns->mutex); cdns->roles[role]->stop(cdns); cdns->roles[role]->state = CDNS_ROLE_STATE_INACTIVE; mutex_unlock(&cdns->mutex); } static void cdns_exit_roles(struct cdns *cdns) { cdns_role_stop(cdns); cdns_drd_exit(cdns); } /** * cdns_core_init_role - initialize role of operation * @cdns: Pointer to cdns structure * * Returns 0 on success otherwise negative errno */ static int cdns_core_init_role(struct cdns *cdns) { struct device *dev = cdns->dev; enum usb_dr_mode best_dr_mode; enum usb_dr_mode dr_mode; int ret; dr_mode = usb_get_dr_mode(dev); cdns->role = USB_ROLE_NONE; /* * If driver can't read mode by means of usb_get_dr_mode function then * chooses mode according with Kernel configuration. This setting * can be restricted later depending on strap pin configuration. */ if (dr_mode == USB_DR_MODE_UNKNOWN) { if (cdns->version == CDNSP_CONTROLLER_V2) { if (IS_ENABLED(CONFIG_USB_CDNSP_HOST) && IS_ENABLED(CONFIG_USB_CDNSP_GADGET)) dr_mode = USB_DR_MODE_OTG; else if (IS_ENABLED(CONFIG_USB_CDNSP_HOST)) dr_mode = USB_DR_MODE_HOST; else if (IS_ENABLED(CONFIG_USB_CDNSP_GADGET)) dr_mode = USB_DR_MODE_PERIPHERAL; } else { if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) && IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) dr_mode = USB_DR_MODE_OTG; else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST)) dr_mode = USB_DR_MODE_HOST; else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) dr_mode = USB_DR_MODE_PERIPHERAL; } } /* * At this point cdns->dr_mode contains strap configuration. * Driver try update this setting considering kernel configuration */ best_dr_mode = cdns->dr_mode; ret = cdns_idle_init(cdns); if (ret) return ret; if (dr_mode == USB_DR_MODE_OTG) { best_dr_mode = cdns->dr_mode; } else if (cdns->dr_mode == USB_DR_MODE_OTG) { best_dr_mode = dr_mode; } else if (cdns->dr_mode != dr_mode) { dev_err(dev, "Incorrect DRD configuration\n"); return -EINVAL; } dr_mode = best_dr_mode; if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { if ((cdns->version == CDNSP_CONTROLLER_V2 && IS_ENABLED(CONFIG_USB_CDNSP_HOST)) || (cdns->version < CDNSP_CONTROLLER_V2 && IS_ENABLED(CONFIG_USB_CDNS3_HOST))) ret = cdns_host_init(cdns); else ret = -ENXIO; if (ret) { dev_err(dev, "Host initialization failed with %d\n", ret); goto err; } } if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) { if (cdns->gadget_init) ret = cdns->gadget_init(cdns); else ret = -ENXIO; if (ret) { dev_err(dev, "Device initialization failed with %d\n", ret); goto err; } } cdns->dr_mode = dr_mode; ret = cdns_drd_update_mode(cdns); if (ret) goto err; /* Initialize idle role to start with */ ret = cdns_role_start(cdns, USB_ROLE_NONE); if (ret) goto err; switch (cdns->dr_mode) { case USB_DR_MODE_OTG: ret = cdns_hw_role_switch(cdns); if (ret) goto err; break; case USB_DR_MODE_PERIPHERAL: ret = cdns_role_start(cdns, USB_ROLE_DEVICE); if (ret) goto err; break; case USB_DR_MODE_HOST: ret = cdns_role_start(cdns, USB_ROLE_HOST); if (ret) goto err; break; default: ret = -EINVAL; goto err; } return 0; err: cdns_exit_roles(cdns); return ret; } /** * cdns_hw_role_state_machine - role switch state machine based on hw events. * @cdns: Pointer to controller structure. * * Returns next role to be entered based on hw events. */ static enum usb_role cdns_hw_role_state_machine(struct cdns *cdns) { enum usb_role role = USB_ROLE_NONE; int id, vbus; if (cdns->dr_mode != USB_DR_MODE_OTG) { if (cdns_is_host(cdns)) role = USB_ROLE_HOST; if (cdns_is_device(cdns)) role = USB_ROLE_DEVICE; return role; } id = cdns_get_id(cdns); vbus = cdns_get_vbus(cdns); /* * Role change state machine * Inputs: ID, VBUS * Previous state: cdns->role * Next state: role */ role = cdns->role; switch (role) { case USB_ROLE_NONE: /* * Driver treats USB_ROLE_NONE synonymous to IDLE state from * controller specification. */ if (!id) role = USB_ROLE_HOST; else if (vbus) role = USB_ROLE_DEVICE; break; case USB_ROLE_HOST: /* from HOST, we can only change to NONE */ if (id) role = USB_ROLE_NONE; break; case USB_ROLE_DEVICE: /* from GADGET, we can only change to NONE*/ if (!vbus) role = USB_ROLE_NONE; break; } dev_dbg(cdns->dev, "role %d -> %d\n", cdns->role, role); return role; } static int cdns_idle_role_start(struct cdns *cdns) { return 0; } static void cdns_idle_role_stop(struct cdns *cdns) { /* Program Lane swap and bring PHY out of RESET */ phy_reset(cdns->usb3_phy); } static int cdns_idle_init(struct cdns *cdns) { struct cdns_role_driver *rdrv; rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); if (!rdrv) return -ENOMEM; rdrv->start = cdns_idle_role_start; rdrv->stop = cdns_idle_role_stop; rdrv->state = CDNS_ROLE_STATE_INACTIVE; rdrv->suspend = NULL; rdrv->resume = NULL; rdrv->name = "idle"; cdns->roles[USB_ROLE_NONE] = rdrv; return 0; } /** * cdns_hw_role_switch - switch roles based on HW state * @cdns: controller */ int cdns_hw_role_switch(struct cdns *cdns) { enum usb_role real_role, current_role; int ret = 0; /* Depends on role switch class */ if (cdns->role_sw) return 0; pm_runtime_get_sync(cdns->dev); current_role = cdns->role; real_role = cdns_hw_role_state_machine(cdns); /* Do nothing if nothing changed */ if (current_role == real_role) goto exit; cdns_role_stop(cdns); dev_dbg(cdns->dev, "Switching role %d -> %d", current_role, real_role); ret = cdns_role_start(cdns, real_role); if (ret) { /* Back to current role */ dev_err(cdns->dev, "set %d has failed, back to %d\n", real_role, current_role); ret = cdns_role_start(cdns, current_role); if (ret) dev_err(cdns->dev, "back to %d failed too\n", current_role); } exit: pm_runtime_put_sync(cdns->dev); return ret; } /** * cdns_role_get - get current role of controller. * * @sw: pointer to USB role switch structure * * Returns role */ static enum usb_role cdns_role_get(struct usb_role_switch *sw) { struct cdns *cdns = usb_role_switch_get_drvdata(sw); return cdns->role; } /** * cdns_role_set - set current role of controller. * * @sw: pointer to USB role switch structure * @role: the previous role * Handles below events: * - Role switch for dual-role devices * - USB_ROLE_GADGET <--> USB_ROLE_NONE for peripheral-only devices */ static int cdns_role_set(struct usb_role_switch *sw, enum usb_role role) { struct cdns *cdns = usb_role_switch_get_drvdata(sw); int ret = 0; pm_runtime_get_sync(cdns->dev); if (cdns->role == role) goto pm_put; if (cdns->dr_mode == USB_DR_MODE_HOST) { switch (role) { case USB_ROLE_NONE: case USB_ROLE_HOST: break; default: goto pm_put; } } if (cdns->dr_mode == USB_DR_MODE_PERIPHERAL) { switch (role) { case USB_ROLE_NONE: case USB_ROLE_DEVICE: break; default: goto pm_put; } } cdns_role_stop(cdns); ret = cdns_role_start(cdns, role); if (ret) dev_err(cdns->dev, "set role %d has failed\n", role); pm_put: pm_runtime_put_sync(cdns->dev); return ret; } /** * cdns_wakeup_irq - interrupt handler for wakeup events * @irq: irq number for cdns3/cdnsp core device * @data: structure of cdns * * Returns IRQ_HANDLED or IRQ_NONE */ static irqreturn_t cdns_wakeup_irq(int irq, void *data) { struct cdns *cdns = data; if (cdns->in_lpm) { disable_irq_nosync(irq); cdns->wakeup_pending = true; if ((cdns->role == USB_ROLE_HOST) && cdns->host_dev) pm_request_resume(&cdns->host_dev->dev); return IRQ_HANDLED; } return IRQ_NONE; } /** * cdns_init - probe for cdns3/cdnsp core device * @cdns: Pointer to cdns structure. * * Returns 0 on success otherwise negative errno */ int cdns_init(struct cdns *cdns) { struct device *dev = cdns->dev; int ret; ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); if (ret) { dev_err(dev, "error setting dma mask: %d\n", ret); return ret; } mutex_init(&cdns->mutex); if (device_property_read_bool(dev, "usb-role-switch")) { struct usb_role_switch_desc sw_desc = { }; sw_desc.set = cdns_role_set; sw_desc.get = cdns_role_get; sw_desc.allow_userspace_control = true; sw_desc.driver_data = cdns; sw_desc.fwnode = dev->fwnode; cdns->role_sw = usb_role_switch_register(dev, &sw_desc); if (IS_ERR(cdns->role_sw)) { dev_warn(dev, "Unable to register Role Switch\n"); return PTR_ERR(cdns->role_sw); } } if (cdns->wakeup_irq) { ret = devm_request_irq(cdns->dev, cdns->wakeup_irq, cdns_wakeup_irq, IRQF_SHARED, dev_name(cdns->dev), cdns); if (ret) { dev_err(cdns->dev, "couldn't register wakeup irq handler\n"); goto role_switch_unregister; } } ret = cdns_drd_init(cdns); if (ret) goto init_failed; ret = cdns_core_init_role(cdns); if (ret) goto init_failed; spin_lock_init(&cdns->lock); dev_dbg(dev, "Cadence USB3 core: probe succeed\n"); return 0; init_failed: cdns_drd_exit(cdns); role_switch_unregister: if (cdns->role_sw) usb_role_switch_unregister(cdns->role_sw); return ret; } EXPORT_SYMBOL_GPL(cdns_init); /** * cdns_remove - unbind drd driver and clean up * @cdns: Pointer to cdns structure. * * Returns 0 on success otherwise negative errno */ int cdns_remove(struct cdns *cdns) { cdns_exit_roles(cdns); usb_role_switch_unregister(cdns->role_sw); return 0; } EXPORT_SYMBOL_GPL(cdns_remove); #ifdef CONFIG_PM_SLEEP int cdns_suspend(struct cdns *cdns) { struct device *dev = cdns->dev; unsigned long flags; if (pm_runtime_status_suspended(dev)) pm_runtime_resume(dev); if (cdns->roles[cdns->role]->suspend) { spin_lock_irqsave(&cdns->lock, flags); cdns->roles[cdns->role]->suspend(cdns, false); spin_unlock_irqrestore(&cdns->lock, flags); } return 0; } EXPORT_SYMBOL_GPL(cdns_suspend); int cdns_resume(struct cdns *cdns) { enum usb_role real_role; bool role_changed = false; int ret = 0; if (cdns_power_is_lost(cdns)) { if (cdns->role_sw) { cdns->role = cdns_role_get(cdns->role_sw); } else { real_role = cdns_hw_role_state_machine(cdns); if (real_role != cdns->role) { ret = cdns_hw_role_switch(cdns); if (ret) return ret; role_changed = true; } } if (!role_changed) { if (cdns->role == USB_ROLE_HOST) ret = cdns_drd_host_on(cdns); else if (cdns->role == USB_ROLE_DEVICE) ret = cdns_drd_gadget_on(cdns); if (ret) return ret; } } if (cdns->roles[cdns->role]->resume) cdns->roles[cdns->role]->resume(cdns, cdns_power_is_lost(cdns)); return 0; } EXPORT_SYMBOL_GPL(cdns_resume); void cdns_set_active(struct cdns *cdns, u8 set_active) { struct device *dev = cdns->dev; if (set_active) { pm_runtime_disable(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); } return; } EXPORT_SYMBOL_GPL(cdns_set_active); #endif /* CONFIG_PM_SLEEP */ MODULE_AUTHOR("Peter Chen <peter.chen@nxp.com>"); MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>"); MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); MODULE_DESCRIPTION("Cadence USBSS and USBSSP DRD Driver"); MODULE_LICENSE("GPL");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
You can’t perform that action at this time.