diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /drivers/phy/renesas/phy-rcar-gen3-usb2.c | |
download | linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip |
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski:
"Core:
- Add dedicated kmem_cache for typical/small skb->head, avoid having
to access struct page at kfree time, and improve memory use.
- Introduce sysctl to set default RPS configuration for new netdevs.
- Define Netlink protocol specification format which can be used to
describe messages used by each family and auto-generate parsers.
Add tools for generating kernel data structures and uAPI headers.
- Expose all net/core sysctls inside netns.
- Remove 4s sleep in netpoll if carrier is instantly detected on
boot.
- Add configurable limit of MDB entries per port, and port-vlan.
- Continue populating drop reasons throughout the stack.
- Retire a handful of legacy Qdiscs and classifiers.
Protocols:
- Support IPv4 big TCP (TSO frames larger than 64kB).
- Add IP_LOCAL_PORT_RANGE socket option, to control local port range
on socket by socket basis.
- Track and report in procfs number of MPTCP sockets used.
- Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path
manager.
- IPv6: don't check net.ipv6.route.max_size and rely on garbage
collection to free memory (similarly to IPv4).
- Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986).
- ICMP: add per-rate limit counters.
- Add support for user scanning requests in ieee802154.
- Remove static WEP support.
- Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate
reporting.
- WiFi 7 EHT channel puncturing support (client & AP).
BPF:
- Add a rbtree data structure following the "next-gen data structure"
precedent set by recently added linked list, that is, by using
kfunc + kptr instead of adding a new BPF map type.
- Expose XDP hints via kfuncs with initial support for RX hash and
timestamp metadata.
- Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to
better support decap on GRE tunnel devices not operating in collect
metadata.
- Improve x86 JIT's codegen for PROBE_MEM runtime error checks.
- Remove the need for trace_printk_lock for bpf_trace_printk and
bpf_trace_vprintk helpers.
- Extend libbpf's bpf_tracing.h support for tracing arguments of
kprobes/uprobes and syscall as a special case.
- Significantly reduce the search time for module symbols by
livepatch and BPF.
- Enable cpumasks to be used as kptrs, which is useful for tracing
programs tracking which tasks end up running on which CPUs in
different time intervals.
- Add support for BPF trampoline on s390x and riscv64.
- Add capability to export the XDP features supported by the NIC.
- Add __bpf_kfunc tag for marking kernel functions as kfuncs.
- Add cgroup.memory=nobpf kernel parameter option to disable BPF
memory accounting for container environments.
Netfilter:
- Remove the CLUSTERIP target. It has been marked as obsolete for
years, and we still have WARN splats wrt races of the out-of-band
/proc interface installed by this target.
- Add 'destroy' commands to nf_tables. They are identical to the
existing 'delete' commands, but do not return an error if the
referenced object (set, chain, rule...) did not exist.
Driver API:
- Improve cpumask_local_spread() locality to help NICs set the right
IRQ affinity on AMD platforms.
- Separate C22 and C45 MDIO bus transactions more clearly.
- Introduce new DCB table to control DSCP rewrite on egress.
- Support configuration of Physical Layer Collision Avoidance (PLCA)
Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of
shared medium Ethernet.
- Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing
preemption of low priority frames by high priority frames.
- Add support for controlling MACSec offload using netlink SET.
- Rework devlink instance refcounts to allow registration and
de-registration under the instance lock. Split the code into
multiple files, drop some of the unnecessarily granular locks and
factor out common parts of netlink operation handling.
- Add TX frame aggregation parameters (for USB drivers).
- Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning
messages with notifications for debug.
- Allow offloading of UDP NEW connections via act_ct.
- Add support for per action HW stats in TC.
- Support hardware miss to TC action (continue processing in SW from
a specific point in the action chain).
- Warn if old Wireless Extension user space interface is used with
modern cfg80211/mac80211 drivers. Do not support Wireless
Extensions for Wi-Fi 7 devices at all. Everyone should switch to
using nl80211 interface instead.
- Improve the CAN bit timing configuration. Use extack to return
error messages directly to user space, update the SJW handling,
including the definition of a new default value that will benefit
CAN-FD controllers, by increasing their oscillator tolerance.
New hardware / drivers:
- Ethernet:
- nVidia BlueField-3 support (control traffic driver)
- Ethernet support for imx93 SoCs
- Motorcomm yt8531 gigabit Ethernet PHY
- onsemi NCN26000 10BASE-T1S PHY (with support for PLCA)
- Microchip LAN8841 PHY (incl. cable diagnostics and PTP)
- Amlogic gxl MDIO mux
- WiFi:
- RealTek RTL8188EU (rtl8xxxu)
- Qualcomm Wi-Fi 7 devices (ath12k)
- CAN:
- Renesas R-Car V4H
Drivers:
- Bluetooth:
- Set Per Platform Antenna Gain (PPAG) for Intel controllers.
- Ethernet NICs:
- Intel (1G, igc):
- support TSN / Qbv / packet scheduling features of i226 model
- Intel (100G, ice):
- use GNSS subsystem instead of TTY
- multi-buffer XDP support
- extend support for GPIO pins to E823 devices
- nVidia/Mellanox:
- update the shared buffer configuration on PFC commands
- implement PTP adjphase function for HW offset control
- TC support for Geneve and GRE with VF tunnel offload
- more efficient crypto key management method
- multi-port eswitch support
- Netronome/Corigine:
- add DCB IEEE support
- support IPsec offloading for NFP3800
- Freescale/NXP (enetc):
- support XDP_REDIRECT for XDP non-linear buffers
- improve reconfig, avoid link flap and waiting for idle
- support MAC Merge layer
- Other NICs:
- sfc/ef100: add basic devlink support for ef100
- ionic: rx_push mode operation (writing descriptors via MMIO)
- bnxt: use the auxiliary bus abstraction for RDMA
- r8169: disable ASPM and reset bus in case of tx timeout
- cpsw: support QSGMII mode for J721e CPSW9G
- cpts: support pulse-per-second output
- ngbe: add an mdio bus driver
- usbnet: optimize usbnet_bh() by avoiding unnecessary queuing
- r8152: handle devices with FW with NCM support
- amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation
- virtio-net: support multi buffer XDP
- virtio/vsock: replace virtio_vsock_pkt with sk_buff
- tsnep: XDP support
- Ethernet high-speed switches:
- nVidia/Mellanox (mlxsw):
- add support for latency TLV (in FW control messages)
- Microchip (sparx5):
- separate explicit and implicit traffic forwarding rules, make
the implicit rules always active
- add support for egress DSCP rewrite
- IS0 VCAP support (Ingress Classification)
- IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS
etc.)
- ES2 VCAP support (Egress Access Control)
- support for Per-Stream Filtering and Policing (802.1Q,
8.6.5.1)
- Ethernet embedded switches:
- Marvell (mv88e6xxx):
- add MAB (port auth) offload support
- enable PTP receive for mv88e6390
- NXP (ocelot):
- support MAC Merge layer
- support for the the vsc7512 internal copper phys
- Microchip:
- lan9303: convert to PHYLINK
- lan966x: support TC flower filter statistics
- lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x
- lan937x: support Credit Based Shaper configuration
- ksz9477: support Energy Efficient Ethernet
- other:
- qca8k: convert to regmap read/write API, use bulk operations
- rswitch: Improve TX timestamp accuracy
- Intel WiFi (iwlwifi):
- EHT (Wi-Fi 7) rate reporting
- STEP equalizer support: transfer some STEP (connection to radio
on platforms with integrated wifi) related parameters from the
BIOS to the firmware.
- Qualcomm 802.11ax WiFi (ath11k):
- IPQ5018 support
- Fine Timing Measurement (FTM) responder role support
- channel 177 support
- MediaTek WiFi (mt76):
- per-PHY LED support
- mt7996: EHT (Wi-Fi 7) support
- Wireless Ethernet Dispatch (WED) reset support
- switch to using page pool allocator
- RealTek WiFi (rtw89):
- support new version of Bluetooth co-existance
- Mobile:
- rmnet: support TX aggregation"
* tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits)
page_pool: add a comment explaining the fragment counter usage
net: ethtool: fix __ethtool_dev_mm_supported() implementation
ethtool: pse-pd: Fix double word in comments
xsk: add linux/vmalloc.h to xsk.c
sefltests: netdevsim: wait for devlink instance after netns removal
selftest: fib_tests: Always cleanup before exit
net/mlx5e: Align IPsec ASO result memory to be as required by hardware
net/mlx5e: TC, Set CT miss to the specific ct action instance
net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG
net/mlx5: Refactor tc miss handling to a single function
net/mlx5: Kconfig: Make tc offload depend on tc skb extension
net/sched: flower: Support hardware miss to tc action
net/sched: flower: Move filter handle initialization earlier
net/sched: cls_api: Support hardware miss to tc action
net/sched: Rename user cookie and act cookie
sfc: fix builds without CONFIG_RTC_LIB
sfc: clean up some inconsistent indentings
net/mlx4_en: Introduce flexible array to silence overflow warning
net: lan966x: Fix possible deadlock inside PTP
net/ulp: Remove redundant ->clone() test in inet_clone_ulp().
...
Diffstat (limited to 'drivers/phy/renesas/phy-rcar-gen3-usb2.c')
-rw-r--r-- | drivers/phy/renesas/phy-rcar-gen3-usb2.c | 782 |
1 files changed, 782 insertions, 0 deletions
diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c new file mode 100644 index 000000000..9de617ca9 --- /dev/null +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas R-Car Gen3 for USB2.0 PHY driver + * + * Copyright (C) 2015-2017 Renesas Electronics Corporation + * + * This is based on the phy-rcar-gen2 driver: + * Copyright (C) 2014 Renesas Solutions Corp. + * Copyright (C) 2014 Cogent Embedded, Inc. + */ + +#include <linux/extcon-provider.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/string.h> +#include <linux/usb/of.h> +#include <linux/workqueue.h> + +/******* USB2.0 Host registers (original offset is +0x200) *******/ +#define USB2_INT_ENABLE 0x000 +#define USB2_USBCTR 0x00c +#define USB2_SPD_RSM_TIMSET 0x10c +#define USB2_OC_TIMSET 0x110 +#define USB2_COMMCTRL 0x600 +#define USB2_OBINTSTA 0x604 +#define USB2_OBINTEN 0x608 +#define USB2_VBCTRL 0x60c +#define USB2_LINECTRL1 0x610 +#define USB2_ADPCTRL 0x630 + +/* INT_ENABLE */ +#define USB2_INT_ENABLE_UCOM_INTEN BIT(3) +#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2) /* For EHCI */ +#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1) /* For OHCI */ + +/* USBCTR */ +#define USB2_USBCTR_DIRPD BIT(2) +#define USB2_USBCTR_PLL_RST BIT(1) + +/* SPD_RSM_TIMSET */ +#define USB2_SPD_RSM_TIMSET_INIT 0x014e029b + +/* OC_TIMSET */ +#define USB2_OC_TIMSET_INIT 0x000209ab + +/* COMMCTRL */ +#define USB2_COMMCTRL_OTG_PERI BIT(31) /* 1 = Peripheral mode */ + +/* OBINTSTA and OBINTEN */ +#define USB2_OBINT_SESSVLDCHG BIT(12) +#define USB2_OBINT_IDDIGCHG BIT(11) +#define USB2_OBINT_BITS (USB2_OBINT_SESSVLDCHG | \ + USB2_OBINT_IDDIGCHG) + +/* VBCTRL */ +#define USB2_VBCTRL_OCCLREN BIT(16) +#define USB2_VBCTRL_DRVVBUSSEL BIT(8) +#define USB2_VBCTRL_VBOUT BIT(0) + +/* LINECTRL1 */ +#define USB2_LINECTRL1_DPRPD_EN BIT(19) +#define USB2_LINECTRL1_DP_RPD BIT(18) +#define USB2_LINECTRL1_DMRPD_EN BIT(17) +#define USB2_LINECTRL1_DM_RPD BIT(16) +#define USB2_LINECTRL1_OPMODE_NODRV BIT(6) + +/* ADPCTRL */ +#define USB2_ADPCTRL_OTGSESSVLD BIT(20) +#define USB2_ADPCTRL_IDDIG BIT(19) +#define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */ +#define USB2_ADPCTRL_DRVVBUS BIT(4) + +/* RZ/G2L specific */ +#define USB2_OBINT_IDCHG_EN BIT(0) +#define USB2_LINECTRL1_USB2_IDMON BIT(0) + +#define NUM_OF_PHYS 4 +enum rcar_gen3_phy_index { + PHY_INDEX_BOTH_HC, + PHY_INDEX_OHCI, + PHY_INDEX_EHCI, + PHY_INDEX_HSUSB +}; + +static const u32 rcar_gen3_int_enable[NUM_OF_PHYS] = { + USB2_INT_ENABLE_USBH_INTB_EN | USB2_INT_ENABLE_USBH_INTA_EN, + USB2_INT_ENABLE_USBH_INTA_EN, + USB2_INT_ENABLE_USBH_INTB_EN, + 0 +}; + +struct rcar_gen3_phy { + struct phy *phy; + struct rcar_gen3_chan *ch; + u32 int_enable_bits; + bool initialized; + bool otg_initialized; + bool powered; +}; + +struct rcar_gen3_chan { + void __iomem *base; + struct device *dev; /* platform_device's device */ + struct extcon_dev *extcon; + struct rcar_gen3_phy rphys[NUM_OF_PHYS]; + struct regulator *vbus; + struct work_struct work; + struct mutex lock; /* protects rphys[...].powered */ + enum usb_dr_mode dr_mode; + int irq; + u32 obint_enable_bits; + bool extcon_host; + bool is_otg_channel; + bool uses_otg_pins; + bool soc_no_adp_ctrl; +}; + +struct rcar_gen3_phy_drv_data { + const struct phy_ops *phy_usb2_ops; + bool no_adp_ctrl; +}; + +/* + * Combination about is_otg_channel and uses_otg_pins: + * + * Parameters || Behaviors + * is_otg_channel | uses_otg_pins || irqs | role sysfs + * ---------------------+---------------++--------------+------------ + * true | true || enabled | enabled + * true | false || disabled | enabled + * false | any || disabled | disabled + */ + +static void rcar_gen3_phy_usb2_work(struct work_struct *work) +{ + struct rcar_gen3_chan *ch = container_of(work, struct rcar_gen3_chan, + work); + + if (ch->extcon_host) { + extcon_set_state_sync(ch->extcon, EXTCON_USB_HOST, true); + extcon_set_state_sync(ch->extcon, EXTCON_USB, false); + } else { + extcon_set_state_sync(ch->extcon, EXTCON_USB_HOST, false); + extcon_set_state_sync(ch->extcon, EXTCON_USB, true); + } +} + +static void rcar_gen3_set_host_mode(struct rcar_gen3_chan *ch, int host) +{ + void __iomem *usb2_base = ch->base; + u32 val = readl(usb2_base + USB2_COMMCTRL); + + dev_vdbg(ch->dev, "%s: %08x, %d\n", __func__, val, host); + if (host) + val &= ~USB2_COMMCTRL_OTG_PERI; + else + val |= USB2_COMMCTRL_OTG_PERI; + writel(val, usb2_base + USB2_COMMCTRL); +} + +static void rcar_gen3_set_linectrl(struct rcar_gen3_chan *ch, int dp, int dm) +{ + void __iomem *usb2_base = ch->base; + u32 val = readl(usb2_base + USB2_LINECTRL1); + + dev_vdbg(ch->dev, "%s: %08x, %d, %d\n", __func__, val, dp, dm); + val &= ~(USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD); + if (dp) + val |= USB2_LINECTRL1_DP_RPD; + if (dm) + val |= USB2_LINECTRL1_DM_RPD; + writel(val, usb2_base + USB2_LINECTRL1); +} + +static void rcar_gen3_enable_vbus_ctrl(struct rcar_gen3_chan *ch, int vbus) +{ + void __iomem *usb2_base = ch->base; + u32 vbus_ctrl_reg = USB2_ADPCTRL; + u32 vbus_ctrl_val = USB2_ADPCTRL_DRVVBUS; + u32 val; + + dev_vdbg(ch->dev, "%s: %08x, %d\n", __func__, val, vbus); + if (ch->soc_no_adp_ctrl) { + vbus_ctrl_reg = USB2_VBCTRL; + vbus_ctrl_val = USB2_VBCTRL_VBOUT; + } + + val = readl(usb2_base + vbus_ctrl_reg); + if (vbus) + val |= vbus_ctrl_val; + else + val &= ~vbus_ctrl_val; + writel(val, usb2_base + vbus_ctrl_reg); +} + +static void rcar_gen3_control_otg_irq(struct rcar_gen3_chan *ch, int enable) +{ + void __iomem *usb2_base = ch->base; + u32 val = readl(usb2_base + USB2_OBINTEN); + + if (ch->uses_otg_pins && enable) + val |= ch->obint_enable_bits; + else + val &= ~ch->obint_enable_bits; + writel(val, usb2_base + USB2_OBINTEN); +} + +static void rcar_gen3_init_for_host(struct rcar_gen3_chan *ch) +{ + rcar_gen3_set_linectrl(ch, 1, 1); + rcar_gen3_set_host_mode(ch, 1); + rcar_gen3_enable_vbus_ctrl(ch, 1); + + ch->extcon_host = true; + schedule_work(&ch->work); +} + +static void rcar_gen3_init_for_peri(struct rcar_gen3_chan *ch) +{ + rcar_gen3_set_linectrl(ch, 0, 1); + rcar_gen3_set_host_mode(ch, 0); + rcar_gen3_enable_vbus_ctrl(ch, 0); + + ch->extcon_host = false; + schedule_work(&ch->work); +} + +static void rcar_gen3_init_for_b_host(struct rcar_gen3_chan *ch) +{ + void __iomem *usb2_base = ch->base; + u32 val; + + val = readl(usb2_base + USB2_LINECTRL1); + writel(val | USB2_LINECTRL1_OPMODE_NODRV, usb2_base + USB2_LINECTRL1); + + rcar_gen3_set_linectrl(ch, 1, 1); + rcar_gen3_set_host_mode(ch, 1); + rcar_gen3_enable_vbus_ctrl(ch, 0); + + val = readl(usb2_base + USB2_LINECTRL1); + writel(val & ~USB2_LINECTRL1_OPMODE_NODRV, usb2_base + USB2_LINECTRL1); +} + +static void rcar_gen3_init_for_a_peri(struct rcar_gen3_chan *ch) +{ + rcar_gen3_set_linectrl(ch, 0, 1); + rcar_gen3_set_host_mode(ch, 0); + rcar_gen3_enable_vbus_ctrl(ch, 1); +} + +static void rcar_gen3_init_from_a_peri_to_a_host(struct rcar_gen3_chan *ch) +{ + rcar_gen3_control_otg_irq(ch, 0); + + rcar_gen3_enable_vbus_ctrl(ch, 1); + rcar_gen3_init_for_host(ch); + + rcar_gen3_control_otg_irq(ch, 1); +} + +static bool rcar_gen3_check_id(struct rcar_gen3_chan *ch) +{ + if (!ch->uses_otg_pins) + return (ch->dr_mode == USB_DR_MODE_HOST) ? false : true; + + if (ch->soc_no_adp_ctrl) + return !!(readl(ch->base + USB2_LINECTRL1) & USB2_LINECTRL1_USB2_IDMON); + + return !!(readl(ch->base + USB2_ADPCTRL) & USB2_ADPCTRL_IDDIG); +} + +static void rcar_gen3_device_recognition(struct rcar_gen3_chan *ch) +{ + if (!rcar_gen3_check_id(ch)) + rcar_gen3_init_for_host(ch); + else + rcar_gen3_init_for_peri(ch); +} + +static bool rcar_gen3_is_host(struct rcar_gen3_chan *ch) +{ + return !(readl(ch->base + USB2_COMMCTRL) & USB2_COMMCTRL_OTG_PERI); +} + +static enum phy_mode rcar_gen3_get_phy_mode(struct rcar_gen3_chan *ch) +{ + if (rcar_gen3_is_host(ch)) + return PHY_MODE_USB_HOST; + + return PHY_MODE_USB_DEVICE; +} + +static bool rcar_gen3_is_any_rphy_initialized(struct rcar_gen3_chan *ch) +{ + int i; + + for (i = 0; i < NUM_OF_PHYS; i++) { + if (ch->rphys[i].initialized) + return true; + } + + return false; +} + +static bool rcar_gen3_needs_init_otg(struct rcar_gen3_chan *ch) +{ + int i; + + for (i = 0; i < NUM_OF_PHYS; i++) { + if (ch->rphys[i].otg_initialized) + return false; + } + + return true; +} + +static bool rcar_gen3_are_all_rphys_power_off(struct rcar_gen3_chan *ch) +{ + int i; + + for (i = 0; i < NUM_OF_PHYS; i++) { + if (ch->rphys[i].powered) + return false; + } + + return true; +} + +static ssize_t role_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rcar_gen3_chan *ch = dev_get_drvdata(dev); + bool is_b_device; + enum phy_mode cur_mode, new_mode; + + if (!ch->is_otg_channel || !rcar_gen3_is_any_rphy_initialized(ch)) + return -EIO; + + if (sysfs_streq(buf, "host")) + new_mode = PHY_MODE_USB_HOST; + else if (sysfs_streq(buf, "peripheral")) + new_mode = PHY_MODE_USB_DEVICE; + else + return -EINVAL; + + /* is_b_device: true is B-Device. false is A-Device. */ + is_b_device = rcar_gen3_check_id(ch); + cur_mode = rcar_gen3_get_phy_mode(ch); + + /* If current and new mode is the same, this returns the error */ + if (cur_mode == new_mode) + return -EINVAL; + + if (new_mode == PHY_MODE_USB_HOST) { /* And is_host must be false */ + if (!is_b_device) /* A-Peripheral */ + rcar_gen3_init_from_a_peri_to_a_host(ch); + else /* B-Peripheral */ + rcar_gen3_init_for_b_host(ch); + } else { /* And is_host must be true */ + if (!is_b_device) /* A-Host */ + rcar_gen3_init_for_a_peri(ch); + else /* B-Host */ + rcar_gen3_init_for_peri(ch); + } + + return count; +} + +static ssize_t role_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct rcar_gen3_chan *ch = dev_get_drvdata(dev); + + if (!ch->is_otg_channel || !rcar_gen3_is_any_rphy_initialized(ch)) + return -EIO; + + return sprintf(buf, "%s\n", rcar_gen3_is_host(ch) ? "host" : + "peripheral"); +} +static DEVICE_ATTR_RW(role); + +static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) +{ + void __iomem *usb2_base = ch->base; + u32 val; + + /* Should not use functions of read-modify-write a register */ + val = readl(usb2_base + USB2_LINECTRL1); + val = (val & ~USB2_LINECTRL1_DP_RPD) | USB2_LINECTRL1_DPRPD_EN | + USB2_LINECTRL1_DMRPD_EN | USB2_LINECTRL1_DM_RPD; + writel(val, usb2_base + USB2_LINECTRL1); + + if (!ch->soc_no_adp_ctrl) { + val = readl(usb2_base + USB2_VBCTRL); + val &= ~USB2_VBCTRL_OCCLREN; + writel(val | USB2_VBCTRL_DRVVBUSSEL, usb2_base + USB2_VBCTRL); + val = readl(usb2_base + USB2_ADPCTRL); + writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL); + } + msleep(20); + + writel(0xffffffff, usb2_base + USB2_OBINTSTA); + writel(ch->obint_enable_bits, usb2_base + USB2_OBINTEN); + + rcar_gen3_device_recognition(ch); +} + +static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) +{ + struct rcar_gen3_chan *ch = _ch; + void __iomem *usb2_base = ch->base; + u32 status = readl(usb2_base + USB2_OBINTSTA); + irqreturn_t ret = IRQ_NONE; + + if (status & ch->obint_enable_bits) { + dev_vdbg(ch->dev, "%s: %08x\n", __func__, status); + writel(ch->obint_enable_bits, usb2_base + USB2_OBINTSTA); + rcar_gen3_device_recognition(ch); + ret = IRQ_HANDLED; + } + + return ret; +} + +static int rcar_gen3_phy_usb2_init(struct phy *p) +{ + struct rcar_gen3_phy *rphy = phy_get_drvdata(p); + struct rcar_gen3_chan *channel = rphy->ch; + void __iomem *usb2_base = channel->base; + u32 val; + int ret; + + if (!rcar_gen3_is_any_rphy_initialized(channel) && channel->irq >= 0) { + INIT_WORK(&channel->work, rcar_gen3_phy_usb2_work); + ret = request_irq(channel->irq, rcar_gen3_phy_usb2_irq, + IRQF_SHARED, dev_name(channel->dev), channel); + if (ret < 0) { + dev_err(channel->dev, "No irq handler (%d)\n", channel->irq); + return ret; + } + } + + /* Initialize USB2 part */ + val = readl(usb2_base + USB2_INT_ENABLE); + val |= USB2_INT_ENABLE_UCOM_INTEN | rphy->int_enable_bits; + writel(val, usb2_base + USB2_INT_ENABLE); + writel(USB2_SPD_RSM_TIMSET_INIT, usb2_base + USB2_SPD_RSM_TIMSET); + writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET); + + /* Initialize otg part */ + if (channel->is_otg_channel) { + if (rcar_gen3_needs_init_otg(channel)) + rcar_gen3_init_otg(channel); + rphy->otg_initialized = true; + } + + rphy->initialized = true; + + return 0; +} + +static int rcar_gen3_phy_usb2_exit(struct phy *p) +{ + struct rcar_gen3_phy *rphy = phy_get_drvdata(p); + struct rcar_gen3_chan *channel = rphy->ch; + void __iomem *usb2_base = channel->base; + u32 val; + + rphy->initialized = false; + + if (channel->is_otg_channel) + rphy->otg_initialized = false; + + val = readl(usb2_base + USB2_INT_ENABLE); + val &= ~rphy->int_enable_bits; + if (!rcar_gen3_is_any_rphy_initialized(channel)) + val &= ~USB2_INT_ENABLE_UCOM_INTEN; + writel(val, usb2_base + USB2_INT_ENABLE); + + if (channel->irq >= 0 && !rcar_gen3_is_any_rphy_initialized(channel)) + free_irq(channel->irq, channel); + + return 0; +} + +static int rcar_gen3_phy_usb2_power_on(struct phy *p) +{ + struct rcar_gen3_phy *rphy = phy_get_drvdata(p); + struct rcar_gen3_chan *channel = rphy->ch; + void __iomem *usb2_base = channel->base; + u32 val; + int ret = 0; + + mutex_lock(&channel->lock); + if (!rcar_gen3_are_all_rphys_power_off(channel)) + goto out; + + if (channel->vbus) { + ret = regulator_enable(channel->vbus); + if (ret) + goto out; + } + + val = readl(usb2_base + USB2_USBCTR); + val |= USB2_USBCTR_PLL_RST; + writel(val, usb2_base + USB2_USBCTR); + val &= ~USB2_USBCTR_PLL_RST; + writel(val, usb2_base + USB2_USBCTR); + +out: + /* The powered flag should be set for any other phys anyway */ + rphy->powered = true; + mutex_unlock(&channel->lock); + + return 0; +} + +static int rcar_gen3_phy_usb2_power_off(struct phy *p) +{ + struct rcar_gen3_phy *rphy = phy_get_drvdata(p); + struct rcar_gen3_chan *channel = rphy->ch; + int ret = 0; + + mutex_lock(&channel->lock); + rphy->powered = false; + + if (!rcar_gen3_are_all_rphys_power_off(channel)) + goto out; + + if (channel->vbus) + ret = regulator_disable(channel->vbus); + +out: + mutex_unlock(&channel->lock); + + return ret; +} + +static const struct phy_ops rcar_gen3_phy_usb2_ops = { + .init = rcar_gen3_phy_usb2_init, + .exit = rcar_gen3_phy_usb2_exit, + .power_on = rcar_gen3_phy_usb2_power_on, + .power_off = rcar_gen3_phy_usb2_power_off, + .owner = THIS_MODULE, +}; + +static const struct phy_ops rz_g1c_phy_usb2_ops = { + .init = rcar_gen3_phy_usb2_init, + .exit = rcar_gen3_phy_usb2_exit, + .owner = THIS_MODULE, +}; + +static const struct rcar_gen3_phy_drv_data rcar_gen3_phy_usb2_data = { + .phy_usb2_ops = &rcar_gen3_phy_usb2_ops, + .no_adp_ctrl = false, +}; + +static const struct rcar_gen3_phy_drv_data rz_g1c_phy_usb2_data = { + .phy_usb2_ops = &rz_g1c_phy_usb2_ops, + .no_adp_ctrl = false, +}; + +static const struct rcar_gen3_phy_drv_data rz_g2l_phy_usb2_data = { + .phy_usb2_ops = &rcar_gen3_phy_usb2_ops, + .no_adp_ctrl = true, +}; + +static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { + { + .compatible = "renesas,usb2-phy-r8a77470", + .data = &rz_g1c_phy_usb2_data, + }, + { + .compatible = "renesas,usb2-phy-r8a7795", + .data = &rcar_gen3_phy_usb2_data, + }, + { + .compatible = "renesas,usb2-phy-r8a7796", + .data = &rcar_gen3_phy_usb2_data, + }, + { + .compatible = "renesas,usb2-phy-r8a77965", + .data = &rcar_gen3_phy_usb2_data, + }, + { + .compatible = "renesas,rzg2l-usb2-phy", + .data = &rz_g2l_phy_usb2_data, + }, + { + .compatible = "renesas,rcar-gen3-usb2-phy", + .data = &rcar_gen3_phy_usb2_data, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table); + +static const unsigned int rcar_gen3_phy_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +static struct phy *rcar_gen3_phy_usb2_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct rcar_gen3_chan *ch = dev_get_drvdata(dev); + + if (args->args_count == 0) /* For old version dts */ + return ch->rphys[PHY_INDEX_BOTH_HC].phy; + else if (args->args_count > 1) /* Prevent invalid args count */ + return ERR_PTR(-ENODEV); + + if (args->args[0] >= NUM_OF_PHYS) + return ERR_PTR(-ENODEV); + + return ch->rphys[args->args[0]].phy; +} + +static enum usb_dr_mode rcar_gen3_get_dr_mode(struct device_node *np) +{ + enum usb_dr_mode candidate = USB_DR_MODE_UNKNOWN; + int i; + + /* + * If one of device nodes has other dr_mode except UNKNOWN, + * this function returns UNKNOWN. To achieve backward compatibility, + * this loop starts the index as 0. + */ + for (i = 0; i < NUM_OF_PHYS; i++) { + enum usb_dr_mode mode = of_usb_get_dr_mode_by_phy(np, i); + + if (mode != USB_DR_MODE_UNKNOWN) { + if (candidate == USB_DR_MODE_UNKNOWN) + candidate = mode; + else if (candidate != mode) + return USB_DR_MODE_UNKNOWN; + } + } + + return candidate; +} + +static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) +{ + const struct rcar_gen3_phy_drv_data *phy_data; + struct device *dev = &pdev->dev; + struct rcar_gen3_chan *channel; + struct phy_provider *provider; + int ret = 0, i; + + if (!dev->of_node) { + dev_err(dev, "This driver needs device tree\n"); + return -EINVAL; + } + + channel = devm_kzalloc(dev, sizeof(*channel), GFP_KERNEL); + if (!channel) + return -ENOMEM; + + channel->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(channel->base)) + return PTR_ERR(channel->base); + + channel->obint_enable_bits = USB2_OBINT_BITS; + /* get irq number here and request_irq for OTG in phy_init */ + channel->irq = platform_get_irq_optional(pdev, 0); + channel->dr_mode = rcar_gen3_get_dr_mode(dev->of_node); + if (channel->dr_mode != USB_DR_MODE_UNKNOWN) { + int ret; + + channel->is_otg_channel = true; + channel->uses_otg_pins = !of_property_read_bool(dev->of_node, + "renesas,no-otg-pins"); + channel->extcon = devm_extcon_dev_allocate(dev, + rcar_gen3_phy_cable); + if (IS_ERR(channel->extcon)) + return PTR_ERR(channel->extcon); + + ret = devm_extcon_dev_register(dev, channel->extcon); + if (ret < 0) { + dev_err(dev, "Failed to register extcon\n"); + return ret; + } + } + + /* + * devm_phy_create() will call pm_runtime_enable(&phy->dev); + * And then, phy-core will manage runtime pm for this device. + */ + pm_runtime_enable(dev); + + phy_data = of_device_get_match_data(dev); + if (!phy_data) { + ret = -EINVAL; + goto error; + } + + channel->soc_no_adp_ctrl = phy_data->no_adp_ctrl; + if (phy_data->no_adp_ctrl) + channel->obint_enable_bits = USB2_OBINT_IDCHG_EN; + + mutex_init(&channel->lock); + for (i = 0; i < NUM_OF_PHYS; i++) { + channel->rphys[i].phy = devm_phy_create(dev, NULL, + phy_data->phy_usb2_ops); + if (IS_ERR(channel->rphys[i].phy)) { + dev_err(dev, "Failed to create USB2 PHY\n"); + ret = PTR_ERR(channel->rphys[i].phy); + goto error; + } + channel->rphys[i].ch = channel; + channel->rphys[i].int_enable_bits = rcar_gen3_int_enable[i]; + phy_set_drvdata(channel->rphys[i].phy, &channel->rphys[i]); + } + + channel->vbus = devm_regulator_get_optional(dev, "vbus"); + if (IS_ERR(channel->vbus)) { + if (PTR_ERR(channel->vbus) == -EPROBE_DEFER) { + ret = PTR_ERR(channel->vbus); + goto error; + } + channel->vbus = NULL; + } + + platform_set_drvdata(pdev, channel); + channel->dev = dev; + + provider = devm_of_phy_provider_register(dev, rcar_gen3_phy_usb2_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "Failed to register PHY provider\n"); + ret = PTR_ERR(provider); + goto error; + } else if (channel->is_otg_channel) { + int ret; + + ret = device_create_file(dev, &dev_attr_role); + if (ret < 0) + goto error; + } + + return 0; + +error: + pm_runtime_disable(dev); + + return ret; +} + +static int rcar_gen3_phy_usb2_remove(struct platform_device *pdev) +{ + struct rcar_gen3_chan *channel = platform_get_drvdata(pdev); + + if (channel->is_otg_channel) + device_remove_file(&pdev->dev, &dev_attr_role); + + pm_runtime_disable(&pdev->dev); + + return 0; +}; + +static struct platform_driver rcar_gen3_phy_usb2_driver = { + .driver = { + .name = "phy_rcar_gen3_usb2", + .of_match_table = rcar_gen3_phy_usb2_match_table, + }, + .probe = rcar_gen3_phy_usb2_probe, + .remove = rcar_gen3_phy_usb2_remove, +}; +module_platform_driver(rcar_gen3_phy_usb2_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas R-Car Gen3 USB 2.0 PHY"); +MODULE_AUTHOR("Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>"); |