Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
# SPDX-License-Identifier: GPL-2.0
config USB_CHIPIDEA
tristate "ChipIdea Highspeed Dual Role Controller"
depends on ((USB_EHCI_HCD && USB_GADGET) || (USB_EHCI_HCD && !USB_GADGET) || (!USB_EHCI_HCD && USB_GADGET)) && HAS_DMA
select EXTCON
select RESET_CONTROLLER
select USB_ULPI_BUS
select USB_ROLE_SWITCH
select USB_TEGRA_PHY if ARCH_TEGRA
help
Say Y here if your system has a dual role high speed USB
controller based on ChipIdea silicon IP. It supports:
Dual-role switch (ID, OTG FSM, sysfs), Host-only, and
Peripheral-only.
When compiled dynamically, the module will be called ci_hdrc.ko.
if USB_CHIPIDEA
config USB_CHIPIDEA_UDC
bool "ChipIdea device controller"
depends on USB_GADGET
help
Say Y here to enable device controller functionality of the
ChipIdea driver.
config USB_CHIPIDEA_HOST
bool "ChipIdea host controller"
depends on USB_EHCI_HCD
select USB_EHCI_ROOT_HUB_TT
help
Say Y here to enable host controller functionality of the
ChipIdea driver.
config USB_CHIPIDEA_PCI
tristate "Enable PCI glue driver" if EXPERT
depends on USB_PCI
depends on NOP_USB_XCEIV
default USB_CHIPIDEA
config USB_CHIPIDEA_MSM
tristate "Enable MSM hsusb glue driver" if EXPERT
default USB_CHIPIDEA
config USB_CHIPIDEA_NPCM
tristate "Enable NPCM hsusb glue driver" if EXPERT
default USB_CHIPIDEA
config USB_CHIPIDEA_IMX
tristate "Enable i.MX USB glue driver" if EXPERT
depends on OF
default USB_CHIPIDEA
config USB_CHIPIDEA_GENERIC
tristate "Enable generic USB2 glue driver" if EXPERT
default USB_CHIPIDEA
config USB_CHIPIDEA_TEGRA
tristate "Enable Tegra USB glue driver" if EXPERT
depends on OF
default USB_CHIPIDEA
endif
+19
View File
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-2.0
# define_trace.h needs to know how to find our header
CFLAGS_trace.o := -I$(src)
obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc.o
ci_hdrc-y := core.o otg.o debug.o ulpi.o
ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC) += udc.o trace.o
ci_hdrc-$(CONFIG_USB_CHIPIDEA_HOST) += host.o
ci_hdrc-$(CONFIG_USB_OTG_FSM) += otg_fsm.o
# Glue/Bridge layers go here
obj-$(CONFIG_USB_CHIPIDEA_GENERIC) += ci_hdrc_usb2.o
obj-$(CONFIG_USB_CHIPIDEA_MSM) += ci_hdrc_msm.o
obj-$(CONFIG_USB_CHIPIDEA_NPCM) += ci_hdrc_npcm.o
obj-$(CONFIG_USB_CHIPIDEA_PCI) += ci_hdrc_pci.o
obj-$(CONFIG_USB_CHIPIDEA_IMX) += usbmisc_imx.o ci_hdrc_imx.o
obj-$(CONFIG_USB_CHIPIDEA_TEGRA) += ci_hdrc_tegra.o
+147
View File
@@ -0,0 +1,147 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* bits.h - register bits of the ChipIdea USB IP core
*
* Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved.
*
* Author: David Lopo
*/
#ifndef __DRIVERS_USB_CHIPIDEA_BITS_H
#define __DRIVERS_USB_CHIPIDEA_BITS_H
#include <linux/usb/ehci_def.h>
/*
* ID
* For 1.x revision, bit24 - bit31 are reserved
* For 2.x revision, bit25 - bit28 are 0x2
*/
#define TAG (0x1F << 16)
#define REVISION (0xF << 21)
#define VERSION (0xF << 25)
#define CIVERSION (0x7 << 29)
/* SBUSCFG */
#define AHBBRST_MASK 0x7
/* HCCPARAMS */
#define HCCPARAMS_LEN BIT(17)
/* DCCPARAMS */
#define DCCPARAMS_DEN (0x1F << 0)
#define DCCPARAMS_DC BIT(7)
#define DCCPARAMS_HC BIT(8)
/* TESTMODE */
#define TESTMODE_FORCE BIT(0)
/* USBCMD */
#define USBCMD_RS BIT(0)
#define USBCMD_RST BIT(1)
#define USBCMD_SUTW BIT(13)
#define USBCMD_ATDTW BIT(14)
/* USBSTS & USBINTR */
#define USBi_UI BIT(0)
#define USBi_UEI BIT(1)
#define USBi_PCI BIT(2)
#define USBi_URI BIT(6)
#define USBi_SLI BIT(8)
/* DEVICEADDR */
#define DEVICEADDR_USBADRA BIT(24)
#define DEVICEADDR_USBADR (0x7FUL << 25)
/* TTCTRL */
#define TTCTRL_TTHA_MASK (0x7fUL << 24)
/* Set non-zero value for internal TT Hub address representation */
#define TTCTRL_TTHA (0x7fUL << 24)
/* BURSTSIZE */
#define RX_BURST_MASK 0xff
#define TX_BURST_MASK 0xff00
/* PORTSC */
#define PORTSC_CCS BIT(0)
#define PORTSC_CSC BIT(1)
#define PORTSC_PEC BIT(3)
#define PORTSC_OCC BIT(5)
#define PORTSC_FPR BIT(6)
#define PORTSC_SUSP BIT(7)
#define PORTSC_HSP BIT(9)
#define PORTSC_PP BIT(12)
#define PORTSC_PTC (0x0FUL << 16)
#define PORTSC_WKCN BIT(20)
#define PORTSC_PHCD(d) ((d) ? BIT(22) : BIT(23))
/* PTS and PTW for non lpm version only */
#define PORTSC_PFSC BIT(24)
#define PORTSC_PTS(d) \
(u32)((((d) & 0x3) << 30) | (((d) & 0x4) ? BIT(25) : 0))
#define PORTSC_PTW BIT(28)
#define PORTSC_STS BIT(29)
#define PORTSC_W1C_BITS \
(PORTSC_CSC | PORTSC_PEC | PORTSC_OCC)
/* DEVLC */
#define DEVLC_PFSC BIT(23)
#define DEVLC_PSPD (0x03UL << 25)
#define DEVLC_PSPD_HS (0x02UL << 25)
#define DEVLC_PTW BIT(27)
#define DEVLC_STS BIT(28)
#define DEVLC_PTS(d) (u32)(((d) & 0x7) << 29)
/* Encoding for DEVLC_PTS and PORTSC_PTS */
#define PTS_UTMI 0
#define PTS_ULPI 2
#define PTS_SERIAL 3
#define PTS_HSIC 4
/* OTGSC */
#define OTGSC_IDPU BIT(5)
#define OTGSC_HADP BIT(6)
#define OTGSC_HABA BIT(7)
#define OTGSC_ID BIT(8)
#define OTGSC_AVV BIT(9)
#define OTGSC_ASV BIT(10)
#define OTGSC_BSV BIT(11)
#define OTGSC_BSE BIT(12)
#define OTGSC_IDIS BIT(16)
#define OTGSC_AVVIS BIT(17)
#define OTGSC_ASVIS BIT(18)
#define OTGSC_BSVIS BIT(19)
#define OTGSC_BSEIS BIT(20)
#define OTGSC_1MSIS BIT(21)
#define OTGSC_DPIS BIT(22)
#define OTGSC_IDIE BIT(24)
#define OTGSC_AVVIE BIT(25)
#define OTGSC_ASVIE BIT(26)
#define OTGSC_BSVIE BIT(27)
#define OTGSC_BSEIE BIT(28)
#define OTGSC_1MSIE BIT(29)
#define OTGSC_DPIE BIT(30)
#define OTGSC_INT_EN_BITS (OTGSC_IDIE | OTGSC_AVVIE | OTGSC_ASVIE \
| OTGSC_BSVIE | OTGSC_BSEIE | OTGSC_1MSIE \
| OTGSC_DPIE)
#define OTGSC_INT_STATUS_BITS (OTGSC_IDIS | OTGSC_AVVIS | OTGSC_ASVIS \
| OTGSC_BSVIS | OTGSC_BSEIS | OTGSC_1MSIS \
| OTGSC_DPIS)
/* USBMODE */
#define USBMODE_CM (0x03UL << 0)
#define USBMODE_CM_DC (0x02UL << 0)
#define USBMODE_SLOM BIT(3)
#define USBMODE_CI_SDIS BIT(4)
/* ENDPTCTRL */
#define ENDPTCTRL_RXS BIT(0)
#define ENDPTCTRL_RXT (0x03UL << 2)
#define ENDPTCTRL_RXR BIT(6) /* reserved for port 0 */
#define ENDPTCTRL_RXE BIT(7)
#define ENDPTCTRL_TXS BIT(16)
#define ENDPTCTRL_TXT (0x03UL << 18)
#define ENDPTCTRL_TXR BIT(22) /* reserved for port 0 */
#define ENDPTCTRL_TXE BIT(23)
#endif /* __DRIVERS_USB_CHIPIDEA_BITS_H */
+495
View File
@@ -0,0 +1,495 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* ci.h - common structures, functions, and macros of the ChipIdea driver
*
* Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved.
*
* Author: David Lopo
*/
#ifndef __DRIVERS_USB_CHIPIDEA_CI_H
#define __DRIVERS_USB_CHIPIDEA_CI_H
#include <linux/list.h>
#include <linux/irqreturn.h>
#include <linux/usb.h>
#include <linux/usb/gadget.h>
#include <linux/usb/otg-fsm.h>
#include <linux/usb/otg.h>
#include <linux/usb/role.h>
#include <linux/ulpi/interface.h>
/******************************************************************************
* DEFINE
*****************************************************************************/
#define TD_PAGE_COUNT 5
#define CI_HDRC_PAGE_SIZE 4096ul /* page size for TD's */
#define ENDPT_MAX 32
#define CI_MAX_REQ_SIZE (4 * CI_HDRC_PAGE_SIZE)
#define CI_MAX_BUF_SIZE (TD_PAGE_COUNT * CI_HDRC_PAGE_SIZE)
/******************************************************************************
* REGISTERS
*****************************************************************************/
/* Identification Registers */
#define ID_ID 0x0
#define ID_HWGENERAL 0x4
#define ID_HWHOST 0x8
#define ID_HWDEVICE 0xc
#define ID_HWTXBUF 0x10
#define ID_HWRXBUF 0x14
#define ID_SBUSCFG 0x90
/* register indices */
enum ci_hw_regs {
CAP_CAPLENGTH,
CAP_HCCPARAMS,
CAP_DCCPARAMS,
CAP_TESTMODE,
CAP_LAST = CAP_TESTMODE,
OP_USBCMD,
OP_USBSTS,
OP_USBINTR,
OP_FRINDEX,
OP_DEVICEADDR,
OP_ENDPTLISTADDR,
OP_TTCTRL,
OP_BURSTSIZE,
OP_ULPI_VIEWPORT,
OP_PORTSC,
OP_DEVLC,
OP_OTGSC,
OP_USBMODE,
OP_ENDPTSETUPSTAT,
OP_ENDPTPRIME,
OP_ENDPTFLUSH,
OP_ENDPTSTAT,
OP_ENDPTCOMPLETE,
OP_ENDPTCTRL,
/* endptctrl1..15 follow */
OP_LAST = OP_ENDPTCTRL + ENDPT_MAX / 2,
};
/******************************************************************************
* STRUCTURES
*****************************************************************************/
/**
* struct ci_hw_ep - endpoint representation
* @ep: endpoint structure for gadget drivers
* @dir: endpoint direction (TX/RX)
* @num: endpoint number
* @type: endpoint type
* @name: string description of the endpoint
* @qh: queue head for this endpoint
* @wedge: is the endpoint wedged
* @ci: pointer to the controller
* @lock: pointer to controller's spinlock
* @td_pool: pointer to controller's TD pool
*/
struct ci_hw_ep {
struct usb_ep ep;
u8 dir;
u8 num;
u8 type;
char name[16];
struct {
struct list_head queue;
struct ci_hw_qh *ptr;
dma_addr_t dma;
} qh;
int wedge;
/* global resources */
struct ci_hdrc *ci;
spinlock_t *lock;
struct dma_pool *td_pool;
struct td_node *pending_td;
};
enum ci_role {
CI_ROLE_HOST = 0,
CI_ROLE_GADGET,
CI_ROLE_END,
};
enum ci_revision {
CI_REVISION_1X = 10, /* Revision 1.x */
CI_REVISION_20 = 20, /* Revision 2.0 */
CI_REVISION_21, /* Revision 2.1 */
CI_REVISION_22, /* Revision 2.2 */
CI_REVISION_23, /* Revision 2.3 */
CI_REVISION_24, /* Revision 2.4 */
CI_REVISION_25, /* Revision 2.5 */
CI_REVISION_25_PLUS, /* Revision above than 2.5 */
CI_REVISION_UNKNOWN = 99, /* Unknown Revision */
};
/**
* struct ci_role_driver - host/gadget role driver
* @start: start this role
* @stop: stop this role
* @suspend: system suspend handler for this role
* @resume: system resume handler for this role
* @irq: irq handler for this role
* @name: role name string (host/gadget)
*/
struct ci_role_driver {
int (*start)(struct ci_hdrc *);
void (*stop)(struct ci_hdrc *);
void (*suspend)(struct ci_hdrc *ci);
void (*resume)(struct ci_hdrc *ci, bool power_lost);
irqreturn_t (*irq)(struct ci_hdrc *);
const char *name;
};
/**
* struct hw_bank - hardware register mapping representation
* @lpm: set if the device is LPM capable
* @phys: physical address of the controller's registers
* @abs: absolute address of the beginning of register window
* @cap: capability registers
* @op: operational registers
* @size: size of the register window
* @regmap: register lookup table
*/
struct hw_bank {
unsigned lpm;
resource_size_t phys;
void __iomem *abs;
void __iomem *cap;
void __iomem *op;
size_t size;
void __iomem *regmap[OP_LAST + 1];
};
/**
* struct ci_hdrc - chipidea device representation
* @dev: pointer to parent device
* @lock: access synchronization
* @hw_bank: hardware register mapping
* @irq: IRQ number
* @roles: array of supported roles for this controller
* @role: current role
* @is_otg: if the device is otg-capable
* @fsm: otg finite state machine
* @otg_fsm_hrtimer: hrtimer for otg fsm timers
* @hr_timeouts: time out list for active otg fsm timers
* @enabled_otg_timer_bits: bits of enabled otg timers
* @next_otg_timer: next nearest enabled timer to be expired
* @work: work for role changing
* @power_lost_work: work for power lost handling
* @wq: workqueue thread
* @qh_pool: allocation pool for queue heads
* @td_pool: allocation pool for transfer descriptors
* @gadget: device side representation for peripheral controller
* @driver: gadget driver
* @resume_state: save the state of gadget suspend from
* @hw_ep_max: total number of endpoints supported by hardware
* @ci_hw_ep: array of endpoints
* @ep0_dir: ep0 direction
* @ep0out: pointer to ep0 OUT endpoint
* @ep0in: pointer to ep0 IN endpoint
* @status: ep0 status request
* @setaddr: if we should set the address on status completion
* @address: usb address received from the host
* @remote_wakeup: host-enabled remote wakeup
* @suspended: suspended by host
* @test_mode: the selected test mode
* @platdata: platform specific information supplied by parent device
* @vbus_active: is VBUS active
* @ulpi: pointer to ULPI device, if any
* @ulpi_ops: ULPI read/write ops for this device
* @phy: pointer to PHY, if any
* @usb_phy: pointer to USB PHY, if any and if using the USB PHY framework
* @hcd: pointer to usb_hcd for ehci host driver
* @id_event: indicates there is an id event, and handled at ci_otg_work
* @b_sess_valid_event: indicates there is a vbus event, and handled
* at ci_otg_work
* @imx28_write_fix: Freescale imx28 needs swp instruction for writing
* @supports_runtime_pm: if runtime pm is supported
* @in_lpm: if the core in low power mode
* @wakeup_int: if wakeup interrupt occur
* @rev: The revision number for controller
* @mutex: protect code from concorrent running when doing role switch
*/
struct ci_hdrc {
struct device *dev;
spinlock_t lock;
struct hw_bank hw_bank;
int irq;
struct ci_role_driver *roles[CI_ROLE_END];
enum ci_role role;
bool is_otg;
struct usb_otg otg;
struct otg_fsm fsm;
struct hrtimer otg_fsm_hrtimer;
ktime_t hr_timeouts[NUM_OTG_FSM_TIMERS];
unsigned enabled_otg_timer_bits;
enum otg_fsm_timer next_otg_timer;
struct usb_role_switch *role_switch;
struct work_struct work;
struct work_struct power_lost_work;
struct workqueue_struct *wq;
struct dma_pool *qh_pool;
struct dma_pool *td_pool;
struct usb_gadget gadget;
struct usb_gadget_driver *driver;
enum usb_device_state resume_state;
unsigned hw_ep_max;
struct ci_hw_ep ci_hw_ep[ENDPT_MAX];
u32 ep0_dir;
struct ci_hw_ep *ep0out, *ep0in;
struct usb_request *status;
bool setaddr;
u8 address;
u8 remote_wakeup;
u8 suspended;
u8 test_mode;
struct ci_hdrc_platform_data *platdata;
int vbus_active;
struct ulpi *ulpi;
struct ulpi_ops ulpi_ops;
struct phy *phy;
/* old usb_phy interface */
struct usb_phy *usb_phy;
struct usb_hcd *hcd;
bool id_event;
bool b_sess_valid_event;
bool imx28_write_fix;
bool has_portsc_pec_bug;
bool has_short_pkt_limit;
bool supports_runtime_pm;
bool in_lpm;
bool wakeup_int;
enum ci_revision rev;
struct mutex mutex;
};
static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci)
{
BUG_ON(ci->role >= CI_ROLE_END || !ci->roles[ci->role]);
return ci->roles[ci->role];
}
static inline int ci_role_start(struct ci_hdrc *ci, enum ci_role role)
{
int ret;
if (role >= CI_ROLE_END)
return -EINVAL;
if (!ci->roles[role])
return -ENXIO;
ret = ci->roles[role]->start(ci);
if (ret)
return ret;
ci->role = role;
if (ci->usb_phy) {
if (role == CI_ROLE_HOST)
usb_phy_set_event(ci->usb_phy, USB_EVENT_ID);
else
/* in device mode but vbus is invalid*/
usb_phy_set_event(ci->usb_phy, USB_EVENT_NONE);
}
return ret;
}
static inline void ci_role_stop(struct ci_hdrc *ci)
{
enum ci_role role = ci->role;
if (role == CI_ROLE_END)
return;
ci->role = CI_ROLE_END;
ci->roles[role]->stop(ci);
if (ci->usb_phy)
usb_phy_set_event(ci->usb_phy, USB_EVENT_NONE);
}
static inline enum usb_role ci_role_to_usb_role(struct ci_hdrc *ci)
{
if (ci->role == CI_ROLE_HOST)
return USB_ROLE_HOST;
else if (ci->role == CI_ROLE_GADGET && ci->vbus_active)
return USB_ROLE_DEVICE;
else
return USB_ROLE_NONE;
}
static inline enum ci_role usb_role_to_ci_role(enum usb_role role)
{
if (role == USB_ROLE_HOST)
return CI_ROLE_HOST;
else if (role == USB_ROLE_DEVICE)
return CI_ROLE_GADGET;
else
return CI_ROLE_END;
}
/**
* hw_read_id_reg: reads from a identification register
* @ci: the controller
* @offset: offset from the beginning of identification registers region
* @mask: bitfield mask
*
* This function returns register contents
*/
static inline u32 hw_read_id_reg(struct ci_hdrc *ci, u32 offset, u32 mask)
{
return ioread32(ci->hw_bank.abs + offset) & mask;
}
/**
* hw_write_id_reg: writes to a identification register
* @ci: the controller
* @offset: offset from the beginning of identification registers region
* @mask: bitfield mask
* @data: new value
*/
static inline void hw_write_id_reg(struct ci_hdrc *ci, u32 offset,
u32 mask, u32 data)
{
if (~mask)
data = (ioread32(ci->hw_bank.abs + offset) & ~mask)
| (data & mask);
iowrite32(data, ci->hw_bank.abs + offset);
}
/**
* hw_read: reads from a hw register
* @ci: the controller
* @reg: register index
* @mask: bitfield mask
*
* This function returns register contents
*/
static inline u32 hw_read(struct ci_hdrc *ci, enum ci_hw_regs reg, u32 mask)
{
return ioread32(ci->hw_bank.regmap[reg]) & mask;
}
#ifdef CONFIG_SOC_IMX28
static inline void imx28_ci_writel(u32 val, volatile void __iomem *addr)
{
__asm__ ("swp %0, %0, [%1]" : : "r"(val), "r"(addr));
}
#else
static inline void imx28_ci_writel(u32 val, volatile void __iomem *addr)
{
}
#endif
static inline void __hw_write(struct ci_hdrc *ci, u32 val,
void __iomem *addr)
{
if (ci->imx28_write_fix)
imx28_ci_writel(val, addr);
else
iowrite32(val, addr);
}
/**
* hw_write: writes to a hw register
* @ci: the controller
* @reg: register index
* @mask: bitfield mask
* @data: new value
*/
static inline void hw_write(struct ci_hdrc *ci, enum ci_hw_regs reg,
u32 mask, u32 data)
{
if (~mask)
data = (ioread32(ci->hw_bank.regmap[reg]) & ~mask)
| (data & mask);
__hw_write(ci, data, ci->hw_bank.regmap[reg]);
}
/**
* hw_test_and_clear: tests & clears a hw register
* @ci: the controller
* @reg: register index
* @mask: bitfield mask
*
* This function returns register contents
*/
static inline u32 hw_test_and_clear(struct ci_hdrc *ci, enum ci_hw_regs reg,
u32 mask)
{
u32 val = ioread32(ci->hw_bank.regmap[reg]) & mask;
__hw_write(ci, val, ci->hw_bank.regmap[reg]);
return val;
}
/**
* hw_test_and_write: tests & writes a hw register
* @ci: the controller
* @reg: register index
* @mask: bitfield mask
* @data: new value
*
* This function returns register contents
*/
static inline u32 hw_test_and_write(struct ci_hdrc *ci, enum ci_hw_regs reg,
u32 mask, u32 data)
{
u32 val = hw_read(ci, reg, ~0);
hw_write(ci, reg, mask, data);
return (val & mask) >> __ffs(mask);
}
/**
* ci_otg_is_fsm_mode: runtime check if otg controller
* is in otg fsm mode.
*
* @ci: chipidea device
*/
static inline bool ci_otg_is_fsm_mode(struct ci_hdrc *ci)
{
#ifdef CONFIG_USB_OTG_FSM
struct usb_otg_caps *otg_caps = &ci->platdata->ci_otg_caps;
return ci->is_otg && ci->roles[CI_ROLE_HOST] &&
ci->roles[CI_ROLE_GADGET] && (otg_caps->srp_support ||
otg_caps->hnp_support || otg_caps->adp_support);
#else
return false;
#endif
}
int ci_ulpi_init(struct ci_hdrc *ci);
void ci_ulpi_exit(struct ci_hdrc *ci);
int ci_ulpi_resume(struct ci_hdrc *ci);
u32 hw_read_intr_enable(struct ci_hdrc *ci);
u32 hw_read_intr_status(struct ci_hdrc *ci);
int hw_device_reset(struct ci_hdrc *ci);
int hw_port_test_set(struct ci_hdrc *ci, u8 mode);
u8 hw_port_test_get(struct ci_hdrc *ci);
void hw_phymode_configure(struct ci_hdrc *ci);
void ci_platform_configure(struct ci_hdrc *ci);
void dbg_create_files(struct ci_hdrc *ci);
void dbg_remove_files(struct ci_hdrc *ci);
#endif /* __DRIVERS_USB_CHIPIDEA_CI_H */
@@ -0,0 +1,786 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2012 Freescale Semiconductor, Inc.
* Copyright 2025 NXP
* Copyright (C) 2012 Marek Vasut <marex@denx.de>
* on behalf of DENX Software Engineering GmbH
*/
#include <linux/module.h>
#include <linux/irq.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/usb/chipidea.h>
#include <linux/usb/of.h>
#include <linux/clk.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pm_qos.h>
#include "ci.h"
#include "ci_hdrc_imx.h"
struct ci_hdrc_imx_platform_flag {
unsigned int flags;
};
static const struct ci_hdrc_imx_platform_flag imx23_usb_data = {
.flags = CI_HDRC_TURN_VBUS_EARLY_ON |
CI_HDRC_DISABLE_STREAMING,
};
static const struct ci_hdrc_imx_platform_flag imx27_usb_data = {
.flags = CI_HDRC_DISABLE_STREAMING,
};
static const struct ci_hdrc_imx_platform_flag imx28_usb_data = {
.flags = CI_HDRC_IMX28_WRITE_FIX |
CI_HDRC_TURN_VBUS_EARLY_ON |
CI_HDRC_DISABLE_STREAMING,
};
static const struct ci_hdrc_imx_platform_flag imx6q_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_TURN_VBUS_EARLY_ON |
CI_HDRC_DISABLE_STREAMING,
};
static const struct ci_hdrc_imx_platform_flag imx6sl_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_TURN_VBUS_EARLY_ON |
CI_HDRC_DISABLE_HOST_STREAMING,
};
static const struct ci_hdrc_imx_platform_flag imx6sx_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_TURN_VBUS_EARLY_ON |
CI_HDRC_DISABLE_HOST_STREAMING,
};
static const struct ci_hdrc_imx_platform_flag imx6ul_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_TURN_VBUS_EARLY_ON |
CI_HDRC_DISABLE_DEVICE_STREAMING,
};
static const struct ci_hdrc_imx_platform_flag imx7d_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM,
};
static const struct ci_hdrc_imx_platform_flag imx7ulp_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_HAS_PORTSC_PEC_MISSED |
CI_HDRC_PMQOS,
};
static const struct ci_hdrc_imx_platform_flag imx8ulp_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_HAS_PORTSC_PEC_MISSED,
};
static const struct ci_hdrc_imx_platform_flag imx95_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM | CI_HDRC_OUT_BAND_WAKEUP,
};
static const struct ci_hdrc_imx_platform_flag s32g_usb_data = {
.flags = CI_HDRC_DISABLE_HOST_STREAMING,
};
static const struct of_device_id ci_hdrc_imx_dt_ids[] = {
{ .compatible = "fsl,imx23-usb", .data = &imx23_usb_data},
{ .compatible = "fsl,imx28-usb", .data = &imx28_usb_data},
{ .compatible = "fsl,imx27-usb", .data = &imx27_usb_data},
{ .compatible = "fsl,imx6q-usb", .data = &imx6q_usb_data},
{ .compatible = "fsl,imx6sl-usb", .data = &imx6sl_usb_data},
{ .compatible = "fsl,imx6sx-usb", .data = &imx6sx_usb_data},
{ .compatible = "fsl,imx6ul-usb", .data = &imx6ul_usb_data},
{ .compatible = "fsl,imx7d-usb", .data = &imx7d_usb_data},
{ .compatible = "fsl,imx7ulp-usb", .data = &imx7ulp_usb_data},
{ .compatible = "fsl,imx8ulp-usb", .data = &imx8ulp_usb_data},
{ .compatible = "fsl,imx95-usb", .data = &imx95_usb_data},
{ .compatible = "nxp,s32g2-usb", .data = &s32g_usb_data},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids);
struct ci_hdrc_imx_data {
struct usb_phy *phy;
struct platform_device *ci_pdev;
struct clk *clk;
struct clk *clk_wakeup;
struct imx_usbmisc_data *usbmisc_data;
int wakeup_irq;
bool supports_runtime_pm;
bool override_phy_control;
bool in_lpm;
struct pinctrl *pinctrl;
struct pinctrl_state *pinctrl_hsic_active;
struct regulator *hsic_pad_regulator;
/* SoC before i.mx6 (except imx23/imx28) needs three clks */
bool need_three_clks;
struct clk *clk_ipg;
struct clk *clk_ahb;
struct clk *clk_per;
/* --------------------------------- */
struct pm_qos_request pm_qos_req;
const struct ci_hdrc_imx_platform_flag *plat_data;
};
/* Common functions shared by usbmisc drivers */
static struct imx_usbmisc_data *usbmisc_get_init_data(struct device *dev)
{
struct platform_device *misc_pdev;
struct device_node *np = dev->of_node;
struct of_phandle_args args;
struct imx_usbmisc_data *data;
int ret;
/*
* In case the fsl,usbmisc property is not present this device doesn't
* need usbmisc. Return NULL (which is no error here)
*/
if (!of_property_present(np, "fsl,usbmisc"))
return NULL;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return ERR_PTR(-ENOMEM);
ret = of_parse_phandle_with_args(np, "fsl,usbmisc", "#index-cells",
0, &args);
if (ret) {
dev_err(dev, "Failed to parse property fsl,usbmisc, errno %d\n",
ret);
return ERR_PTR(ret);
}
data->index = args.args[0];
misc_pdev = of_find_device_by_node(args.np);
of_node_put(args.np);
if (!misc_pdev)
return ERR_PTR(-EPROBE_DEFER);
if (!platform_get_drvdata(misc_pdev)) {
put_device(&misc_pdev->dev);
return ERR_PTR(-EPROBE_DEFER);
}
data->dev = &misc_pdev->dev;
/*
* Check the various over current related properties. If over current
* detection is disabled we're not interested in the polarity.
*/
if (of_property_read_bool(np, "disable-over-current")) {
data->disable_oc = 1;
} else if (of_property_read_bool(np, "over-current-active-high")) {
data->oc_pol_active_low = 0;
data->oc_pol_configured = 1;
} else if (of_property_read_bool(np, "over-current-active-low")) {
data->oc_pol_active_low = 1;
data->oc_pol_configured = 1;
} else {
dev_warn(dev, "No over current polarity defined\n");
}
data->pwr_pol = of_property_read_bool(np, "power-active-high");
data->evdo = of_property_read_bool(np, "external-vbus-divider");
if (of_usb_get_phy_mode(np) == USBPHY_INTERFACE_MODE_ULPI)
data->ulpi = 1;
if (of_property_read_u32(np, "samsung,picophy-pre-emp-curr-control",
&data->emp_curr_control))
data->emp_curr_control = -1;
if (of_property_read_u32(np, "samsung,picophy-dc-vol-level-adjust",
&data->dc_vol_level_adjust))
data->dc_vol_level_adjust = -1;
if (of_property_read_u32(np, "fsl,picophy-rise-fall-time-adjust",
&data->rise_fall_time_adjust))
data->rise_fall_time_adjust = -1;
return data;
}
/* End of common functions shared by usbmisc drivers*/
static int imx_get_clks(struct device *dev)
{
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
int ret = 0;
data->clk_ipg = devm_clk_get(dev, "ipg");
if (IS_ERR(data->clk_ipg)) {
/* If the platform only needs one primary clock */
data->clk = devm_clk_get(dev, NULL);
if (IS_ERR(data->clk)) {
ret = PTR_ERR(data->clk);
dev_err(dev,
"Failed to get clks, err=%ld,%ld\n",
PTR_ERR(data->clk), PTR_ERR(data->clk_ipg));
return ret;
}
/* Get wakeup clock. Not all of the platforms need to
* handle this clock. So make it optional.
*/
data->clk_wakeup = devm_clk_get_optional(dev, "usb_wakeup");
if (IS_ERR(data->clk_wakeup))
ret = dev_err_probe(dev, PTR_ERR(data->clk_wakeup),
"Failed to get wakeup clk\n");
return ret;
}
data->clk_ahb = devm_clk_get(dev, "ahb");
if (IS_ERR(data->clk_ahb)) {
ret = PTR_ERR(data->clk_ahb);
dev_err(dev,
"Failed to get ahb clock, err=%d\n", ret);
return ret;
}
data->clk_per = devm_clk_get(dev, "per");
if (IS_ERR(data->clk_per)) {
ret = PTR_ERR(data->clk_per);
dev_err(dev,
"Failed to get per clock, err=%d\n", ret);
return ret;
}
data->need_three_clks = true;
return ret;
}
static int imx_prepare_enable_clks(struct device *dev)
{
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
int ret = 0;
if (data->need_three_clks) {
ret = clk_prepare_enable(data->clk_ipg);
if (ret) {
dev_err(dev,
"Failed to prepare/enable ipg clk, err=%d\n",
ret);
return ret;
}
ret = clk_prepare_enable(data->clk_ahb);
if (ret) {
dev_err(dev,
"Failed to prepare/enable ahb clk, err=%d\n",
ret);
clk_disable_unprepare(data->clk_ipg);
return ret;
}
ret = clk_prepare_enable(data->clk_per);
if (ret) {
dev_err(dev,
"Failed to prepare/enable per clk, err=%d\n",
ret);
clk_disable_unprepare(data->clk_ahb);
clk_disable_unprepare(data->clk_ipg);
return ret;
}
} else {
ret = clk_prepare_enable(data->clk);
if (ret) {
dev_err(dev,
"Failed to prepare/enable clk, err=%d\n",
ret);
return ret;
}
}
return ret;
}
static void imx_disable_unprepare_clks(struct device *dev)
{
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
if (data->need_three_clks) {
clk_disable_unprepare(data->clk_per);
clk_disable_unprepare(data->clk_ahb);
clk_disable_unprepare(data->clk_ipg);
} else {
clk_disable_unprepare(data->clk);
}
}
static int ci_hdrc_imx_notify_event(struct ci_hdrc *ci, unsigned int event)
{
struct device *dev = ci->dev->parent;
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
int ret = 0;
struct imx_usbmisc_data *mdata = data->usbmisc_data;
switch (event) {
case CI_HDRC_IMX_HSIC_ACTIVE_EVENT:
if (data->pinctrl) {
ret = pinctrl_select_state(data->pinctrl,
data->pinctrl_hsic_active);
if (ret)
dev_err(dev,
"hsic_active select failed, err=%d\n",
ret);
}
break;
case CI_HDRC_IMX_HSIC_SUSPEND_EVENT:
ret = imx_usbmisc_hsic_set_connect(mdata);
if (ret)
dev_err(dev,
"hsic_set_connect failed, err=%d\n", ret);
break;
case CI_HDRC_CONTROLLER_VBUS_EVENT:
if (ci->vbus_active)
ret = imx_usbmisc_charger_detection(mdata, true);
else
ret = imx_usbmisc_charger_detection(mdata, false);
if (ci->usb_phy)
schedule_work(&ci->usb_phy->chg_work);
break;
case CI_HDRC_CONTROLLER_PULLUP_EVENT:
if (ci->role == CI_ROLE_GADGET &&
ci->gadget.speed == USB_SPEED_HIGH)
imx_usbmisc_pullup(data->usbmisc_data,
ci->gadget.connected);
break;
default:
break;
}
return ret;
}
static irqreturn_t ci_wakeup_irq_handler(int irq, void *data)
{
struct ci_hdrc_imx_data *imx_data = data;
disable_irq_nosync(irq);
pm_runtime_resume(&imx_data->ci_pdev->dev);
return IRQ_HANDLED;
}
static void ci_hdrc_imx_disable_regulator(void *arg)
{
struct ci_hdrc_imx_data *data = arg;
regulator_disable(data->hsic_pad_regulator);
}
static int ci_hdrc_imx_probe(struct platform_device *pdev)
{
struct ci_hdrc_imx_data *data;
struct ci_hdrc_platform_data pdata = {
.name = dev_name(&pdev->dev),
.capoffset = DEF_CAPOFFSET,
.flags = CI_HDRC_HAS_SHORT_PKT_LIMIT,
.notify_event = ci_hdrc_imx_notify_event,
};
int ret;
const struct ci_hdrc_imx_platform_flag *imx_platform_flag;
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
const char *irq_name;
imx_platform_flag = of_device_get_match_data(&pdev->dev);
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->plat_data = imx_platform_flag;
pdata.flags |= imx_platform_flag->flags;
platform_set_drvdata(pdev, data);
data->usbmisc_data = usbmisc_get_init_data(dev);
if (IS_ERR(data->usbmisc_data))
return PTR_ERR(data->usbmisc_data);
if ((of_usb_get_phy_mode(dev->of_node) == USBPHY_INTERFACE_MODE_HSIC)
&& data->usbmisc_data) {
pdata.flags |= CI_HDRC_IMX_IS_HSIC;
data->usbmisc_data->hsic = 1;
data->pinctrl = devm_pinctrl_get(dev);
if (PTR_ERR(data->pinctrl) == -ENODEV)
data->pinctrl = NULL;
else if (IS_ERR(data->pinctrl)) {
ret = dev_err_probe(dev, PTR_ERR(data->pinctrl),
"pinctrl get failed\n");
goto err_put;
}
data->hsic_pad_regulator =
devm_regulator_get_optional(dev, "hsic");
if (PTR_ERR(data->hsic_pad_regulator) == -ENODEV) {
/* no pad regulator is needed */
data->hsic_pad_regulator = NULL;
} else if (IS_ERR(data->hsic_pad_regulator)) {
ret = dev_err_probe(dev, PTR_ERR(data->hsic_pad_regulator),
"Get HSIC pad regulator error\n");
goto err_put;
}
if (data->hsic_pad_regulator) {
ret = regulator_enable(data->hsic_pad_regulator);
if (ret) {
dev_err(dev,
"Failed to enable HSIC pad regulator\n");
goto err_put;
}
ret = devm_add_action_or_reset(dev,
ci_hdrc_imx_disable_regulator, data);
if (ret) {
dev_err(dev,
"Failed to add regulator devm action\n");
goto err_put;
}
}
}
/* HSIC pinctrl handling */
if (data->pinctrl) {
struct pinctrl_state *pinctrl_hsic_idle;
pinctrl_hsic_idle = pinctrl_lookup_state(data->pinctrl, "idle");
if (IS_ERR(pinctrl_hsic_idle)) {
dev_err(dev,
"pinctrl_hsic_idle lookup failed, err=%ld\n",
PTR_ERR(pinctrl_hsic_idle));
ret = PTR_ERR(pinctrl_hsic_idle);
goto err_put;
}
ret = pinctrl_select_state(data->pinctrl, pinctrl_hsic_idle);
if (ret) {
dev_err(dev, "hsic_idle select failed, err=%d\n", ret);
goto err_put;
}
data->pinctrl_hsic_active = pinctrl_lookup_state(data->pinctrl,
"active");
if (IS_ERR(data->pinctrl_hsic_active)) {
dev_err(dev,
"pinctrl_hsic_active lookup failed, err=%ld\n",
PTR_ERR(data->pinctrl_hsic_active));
ret = PTR_ERR(data->pinctrl_hsic_active);
goto err_put;
}
}
if (pdata.flags & CI_HDRC_PMQOS)
cpu_latency_qos_add_request(&data->pm_qos_req, 0);
ret = imx_get_clks(dev);
if (ret)
goto qos_remove_request;
ret = imx_prepare_enable_clks(dev);
if (ret)
goto qos_remove_request;
ret = clk_prepare_enable(data->clk_wakeup);
if (ret)
goto err_wakeup_clk;
data->phy = devm_usb_get_phy_by_phandle(dev, "fsl,usbphy", 0);
if (IS_ERR(data->phy)) {
ret = PTR_ERR(data->phy);
if (ret != -ENODEV) {
dev_err_probe(dev, ret, "Failed to parse fsl,usbphy\n");
goto err_clk;
}
data->phy = devm_usb_get_phy_by_phandle(dev, "phys", 0);
if (IS_ERR(data->phy)) {
ret = PTR_ERR(data->phy);
if (ret == -ENODEV) {
data->phy = NULL;
} else {
dev_err_probe(dev, ret, "Failed to parse phys\n");
goto err_clk;
}
}
}
pdata.usb_phy = data->phy;
if (data->usbmisc_data)
data->usbmisc_data->usb_phy = data->phy;
if ((of_device_is_compatible(np, "fsl,imx53-usb") ||
of_device_is_compatible(np, "fsl,imx51-usb")) && pdata.usb_phy &&
of_usb_get_phy_mode(np) == USBPHY_INTERFACE_MODE_ULPI) {
pdata.flags |= CI_HDRC_OVERRIDE_PHY_CONTROL;
data->override_phy_control = true;
ret = usb_phy_init(pdata.usb_phy);
if (ret) {
dev_err(dev, "Failed to init phy\n");
goto err_clk;
}
}
if (pdata.flags & CI_HDRC_SUPPORTS_RUNTIME_PM)
data->supports_runtime_pm = true;
data->wakeup_irq = platform_get_irq_optional(pdev, 1);
if (data->wakeup_irq > 0) {
irq_name = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", pdata.name);
if (!irq_name) {
dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n");
goto err_clk;
}
ret = devm_request_threaded_irq(dev, data->wakeup_irq,
NULL, ci_wakeup_irq_handler,
IRQF_ONESHOT | IRQF_NO_AUTOEN,
irq_name, data);
if (ret)
goto err_clk;
}
ret = imx_usbmisc_init(data->usbmisc_data);
if (ret) {
dev_err(dev, "usbmisc init failed, ret=%d\n", ret);
goto phy_shutdown;
}
data->ci_pdev = ci_hdrc_add_device(dev,
pdev->resource, pdev->num_resources,
&pdata);
if (IS_ERR(data->ci_pdev)) {
ret = PTR_ERR(data->ci_pdev);
dev_err_probe(dev, ret, "ci_hdrc_add_device failed\n");
goto phy_shutdown;
}
if (data->usbmisc_data) {
if (!IS_ERR(pdata.id_extcon.edev) ||
of_property_read_bool(np, "usb-role-switch"))
data->usbmisc_data->ext_id = 1;
if (!IS_ERR(pdata.vbus_extcon.edev) ||
of_property_read_bool(np, "usb-role-switch"))
data->usbmisc_data->ext_vbus = 1;
/* usbmisc needs to know dr mode to choose wakeup setting */
data->usbmisc_data->available_role =
ci_hdrc_query_available_role(data->ci_pdev);
}
ret = imx_usbmisc_init_post(data->usbmisc_data);
if (ret) {
dev_err(dev, "usbmisc post failed, ret=%d\n", ret);
goto disable_device;
}
if (data->supports_runtime_pm) {
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
}
device_set_wakeup_capable(dev, true);
return 0;
disable_device:
ci_hdrc_remove_device(data->ci_pdev);
phy_shutdown:
if (data->override_phy_control)
usb_phy_shutdown(data->phy);
err_clk:
clk_disable_unprepare(data->clk_wakeup);
err_wakeup_clk:
imx_disable_unprepare_clks(dev);
qos_remove_request:
if (pdata.flags & CI_HDRC_PMQOS)
cpu_latency_qos_remove_request(&data->pm_qos_req);
data->ci_pdev = NULL;
err_put:
if (data->usbmisc_data)
put_device(data->usbmisc_data->dev);
return ret;
}
static void ci_hdrc_imx_remove(struct platform_device *pdev)
{
struct ci_hdrc_imx_data *data = platform_get_drvdata(pdev);
if (data->supports_runtime_pm) {
pm_runtime_get_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
pm_runtime_put_noidle(&pdev->dev);
}
if (data->ci_pdev)
ci_hdrc_remove_device(data->ci_pdev);
if (data->override_phy_control)
usb_phy_shutdown(data->phy);
if (data->ci_pdev) {
imx_disable_unprepare_clks(&pdev->dev);
clk_disable_unprepare(data->clk_wakeup);
if (data->plat_data->flags & CI_HDRC_PMQOS)
cpu_latency_qos_remove_request(&data->pm_qos_req);
}
if (data->usbmisc_data)
put_device(data->usbmisc_data->dev);
}
static void ci_hdrc_imx_shutdown(struct platform_device *pdev)
{
ci_hdrc_imx_remove(pdev);
}
static int imx_controller_suspend(struct device *dev,
pm_message_t msg)
{
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
int ret = 0;
dev_dbg(dev, "at %s\n", __func__);
ret = imx_usbmisc_suspend(data->usbmisc_data,
PMSG_IS_AUTO(msg) || device_may_wakeup(dev));
if (ret) {
dev_err(dev,
"usbmisc suspend failed, ret=%d\n", ret);
return ret;
}
imx_disable_unprepare_clks(dev);
if (data->wakeup_irq > 0)
enable_irq(data->wakeup_irq);
if (data->plat_data->flags & CI_HDRC_PMQOS)
cpu_latency_qos_remove_request(&data->pm_qos_req);
data->in_lpm = true;
return 0;
}
static int imx_controller_resume(struct device *dev,
pm_message_t msg)
{
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
int ret = 0;
dev_dbg(dev, "at %s\n", __func__);
if (!data->in_lpm) {
WARN_ON(1);
return 0;
}
if (data->plat_data->flags & CI_HDRC_PMQOS)
cpu_latency_qos_add_request(&data->pm_qos_req, 0);
if (data->wakeup_irq > 0 &&
!irqd_irq_disabled(irq_get_irq_data(data->wakeup_irq)))
disable_irq_nosync(data->wakeup_irq);
ret = imx_prepare_enable_clks(dev);
if (ret)
return ret;
data->in_lpm = false;
ret = imx_usbmisc_resume(data->usbmisc_data,
PMSG_IS_AUTO(msg) || device_may_wakeup(dev));
if (ret) {
dev_err(dev, "usbmisc resume failed, ret=%d\n", ret);
goto clk_disable;
}
return 0;
clk_disable:
imx_disable_unprepare_clks(dev);
return ret;
}
static int ci_hdrc_imx_suspend(struct device *dev)
{
int ret;
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
if (data->in_lpm)
/* The core's suspend doesn't run */
return 0;
ret = imx_controller_suspend(dev, PMSG_SUSPEND);
if (ret)
return ret;
pinctrl_pm_select_sleep_state(dev);
if (data->wakeup_irq > 0 && device_may_wakeup(dev)) {
enable_irq_wake(data->wakeup_irq);
if (data->plat_data->flags & CI_HDRC_OUT_BAND_WAKEUP)
device_set_out_band_wakeup(dev);
}
return ret;
}
static int ci_hdrc_imx_resume(struct device *dev)
{
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
int ret;
if (data->wakeup_irq > 0 && device_may_wakeup(dev))
disable_irq_wake(data->wakeup_irq);
pinctrl_pm_select_default_state(dev);
ret = imx_controller_resume(dev, PMSG_RESUME);
if (!ret && data->supports_runtime_pm) {
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
}
return ret;
}
static int ci_hdrc_imx_runtime_suspend(struct device *dev)
{
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
if (data->in_lpm) {
WARN_ON(1);
return 0;
}
return imx_controller_suspend(dev, PMSG_AUTO_SUSPEND);
}
static int ci_hdrc_imx_runtime_resume(struct device *dev)
{
return imx_controller_resume(dev, PMSG_AUTO_RESUME);
}
static const struct dev_pm_ops ci_hdrc_imx_pm_ops = {
SYSTEM_SLEEP_PM_OPS(ci_hdrc_imx_suspend, ci_hdrc_imx_resume)
RUNTIME_PM_OPS(ci_hdrc_imx_runtime_suspend, ci_hdrc_imx_runtime_resume, NULL)
};
static struct platform_driver ci_hdrc_imx_driver = {
.probe = ci_hdrc_imx_probe,
.remove = ci_hdrc_imx_remove,
.shutdown = ci_hdrc_imx_shutdown,
.driver = {
.name = "imx_usb",
.of_match_table = ci_hdrc_imx_dt_ids,
.pm = pm_ptr(&ci_hdrc_imx_pm_ops),
},
};
module_platform_driver(ci_hdrc_imx_driver);
MODULE_ALIAS("platform:imx-usb");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("CI HDRC i.MX USB binding");
MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
MODULE_AUTHOR("Richard Zhao <richard.zhao@freescale.com>");
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright 2012 Freescale Semiconductor, Inc.
*/
#ifndef __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H
#define __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H
struct imx_usbmisc_data {
struct device *dev;
int index;
unsigned int disable_oc:1; /* over current detect disabled */
/* true if over-current polarity is active low */
unsigned int oc_pol_active_low:1;
/* true if dt specifies polarity */
unsigned int oc_pol_configured:1;
unsigned int pwr_pol:1; /* power polarity */
unsigned int evdo:1; /* set external vbus divider option */
unsigned int ulpi:1; /* connected to an ULPI phy */
unsigned int hsic:1; /* HSIC controller */
unsigned int ext_id:1; /* ID from exteranl event */
unsigned int ext_vbus:1; /* Vbus from exteranl event */
struct usb_phy *usb_phy;
enum usb_dr_mode available_role; /* runtime usb dr mode */
int emp_curr_control;
int dc_vol_level_adjust;
int rise_fall_time_adjust;
};
int imx_usbmisc_init(struct imx_usbmisc_data *data);
int imx_usbmisc_init_post(struct imx_usbmisc_data *data);
int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *data);
int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect);
int imx_usbmisc_suspend(struct imx_usbmisc_data *data, bool wakeup);
int imx_usbmisc_resume(struct imx_usbmisc_data *data, bool wakeup);
int imx_usbmisc_pullup(struct imx_usbmisc_data *data, bool on);
#endif /* __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H */
@@ -0,0 +1,307 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. */
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/usb/chipidea.h>
#include <linux/clk.h>
#include <linux/reset.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include <linux/io.h>
#include <linux/reset-controller.h>
#include <linux/extcon.h>
#include <linux/of.h>
#include "ci.h"
#define HS_PHY_AHB_MODE 0x0098
#define HS_PHY_GENCONFIG 0x009c
#define HS_PHY_TXFIFO_IDLE_FORCE_DIS BIT(4)
#define HS_PHY_GENCONFIG_2 0x00a0
#define HS_PHY_SESS_VLD_CTRL_EN BIT(7)
#define HS_PHY_ULPI_TX_PKT_EN_CLR_FIX BIT(19)
#define HSPHY_SESS_VLD_CTRL BIT(25)
/* Vendor base starts at 0x200 beyond CI base */
#define HS_PHY_CTRL 0x0040
#define HS_PHY_SEC_CTRL 0x0078
#define HS_PHY_DIG_CLAMP_N BIT(16)
#define HS_PHY_POR_ASSERT BIT(0)
struct ci_hdrc_msm {
struct platform_device *ci;
struct clk *core_clk;
struct clk *iface_clk;
struct clk *fs_clk;
struct ci_hdrc_platform_data pdata;
struct reset_controller_dev rcdev;
bool secondary_phy;
bool hsic;
void __iomem *base;
};
static int
ci_hdrc_msm_por_reset(struct reset_controller_dev *r, unsigned long id)
{
struct ci_hdrc_msm *ci_msm = container_of(r, struct ci_hdrc_msm, rcdev);
void __iomem *addr = ci_msm->base;
u32 val;
if (id)
addr += HS_PHY_SEC_CTRL;
else
addr += HS_PHY_CTRL;
val = readl_relaxed(addr);
val |= HS_PHY_POR_ASSERT;
writel(val, addr);
/*
* wait for minimum 10 microseconds as suggested by manual.
* Use a slightly larger value since the exact value didn't
* work 100% of the time.
*/
udelay(12);
val &= ~HS_PHY_POR_ASSERT;
writel(val, addr);
return 0;
}
static const struct reset_control_ops ci_hdrc_msm_reset_ops = {
.reset = ci_hdrc_msm_por_reset,
};
static int ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event)
{
struct device *dev = ci->dev->parent;
struct ci_hdrc_msm *msm_ci = dev_get_drvdata(dev);
int ret;
switch (event) {
case CI_HDRC_CONTROLLER_RESET_EVENT:
dev_dbg(dev, "CI_HDRC_CONTROLLER_RESET_EVENT received\n");
hw_phymode_configure(ci);
if (msm_ci->secondary_phy) {
u32 val = readl_relaxed(msm_ci->base + HS_PHY_SEC_CTRL);
val |= HS_PHY_DIG_CLAMP_N;
writel_relaxed(val, msm_ci->base + HS_PHY_SEC_CTRL);
}
ret = phy_init(ci->phy);
if (ret)
return ret;
ret = phy_power_on(ci->phy);
if (ret) {
phy_exit(ci->phy);
return ret;
}
/* use AHB transactor, allow posted data writes */
hw_write_id_reg(ci, HS_PHY_AHB_MODE, 0xffffffff, 0x8);
/* workaround for rx buffer collision issue */
hw_write_id_reg(ci, HS_PHY_GENCONFIG,
HS_PHY_TXFIFO_IDLE_FORCE_DIS, 0);
if (!msm_ci->hsic)
hw_write_id_reg(ci, HS_PHY_GENCONFIG_2,
HS_PHY_ULPI_TX_PKT_EN_CLR_FIX, 0);
if (!IS_ERR(ci->platdata->vbus_extcon.edev) || ci->role_switch) {
hw_write_id_reg(ci, HS_PHY_GENCONFIG_2,
HS_PHY_SESS_VLD_CTRL_EN,
HS_PHY_SESS_VLD_CTRL_EN);
hw_write(ci, OP_USBCMD, HSPHY_SESS_VLD_CTRL,
HSPHY_SESS_VLD_CTRL);
}
break;
case CI_HDRC_CONTROLLER_STOPPED_EVENT:
dev_dbg(dev, "CI_HDRC_CONTROLLER_STOPPED_EVENT received\n");
phy_power_off(ci->phy);
phy_exit(ci->phy);
break;
default:
dev_dbg(dev, "unknown ci_hdrc event\n");
break;
}
return 0;
}
static int ci_hdrc_msm_mux_phy(struct ci_hdrc_msm *ci,
struct platform_device *pdev)
{
struct regmap *regmap;
struct device *dev = &pdev->dev;
struct of_phandle_args args;
u32 val;
int ret;
ret = of_parse_phandle_with_fixed_args(dev->of_node, "phy-select", 2, 0,
&args);
if (ret)
return 0;
regmap = syscon_node_to_regmap(args.np);
of_node_put(args.np);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
ret = regmap_write(regmap, args.args[0], args.args[1]);
if (ret)
return ret;
ci->secondary_phy = !!args.args[1];
if (ci->secondary_phy) {
val = readl_relaxed(ci->base + HS_PHY_SEC_CTRL);
val |= HS_PHY_DIG_CLAMP_N;
writel_relaxed(val, ci->base + HS_PHY_SEC_CTRL);
}
return 0;
}
static int ci_hdrc_msm_probe(struct platform_device *pdev)
{
struct ci_hdrc_msm *ci;
struct platform_device *plat_ci;
struct clk *clk;
struct reset_control *reset;
int ret;
struct device_node *ulpi_node, *phy_node;
dev_dbg(&pdev->dev, "ci_hdrc_msm_probe\n");
ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL);
if (!ci)
return -ENOMEM;
platform_set_drvdata(pdev, ci);
ci->pdata.name = "ci_hdrc_msm";
ci->pdata.capoffset = DEF_CAPOFFSET;
ci->pdata.flags = CI_HDRC_REGS_SHARED | CI_HDRC_DISABLE_STREAMING |
CI_HDRC_OVERRIDE_AHB_BURST |
CI_HDRC_OVERRIDE_PHY_CONTROL;
ci->pdata.notify_event = ci_hdrc_msm_notify_event;
reset = devm_reset_control_get(&pdev->dev, "core");
if (IS_ERR(reset))
return PTR_ERR(reset);
ci->core_clk = clk = devm_clk_get(&pdev->dev, "core");
if (IS_ERR(clk))
return PTR_ERR(clk);
ci->iface_clk = clk = devm_clk_get(&pdev->dev, "iface");
if (IS_ERR(clk))
return PTR_ERR(clk);
ci->fs_clk = clk = devm_clk_get_optional(&pdev->dev, "fs");
if (IS_ERR(clk))
return PTR_ERR(clk);
ci->base = devm_platform_ioremap_resource(pdev, 1);
if (IS_ERR(ci->base))
return PTR_ERR(ci->base);
ci->rcdev.owner = THIS_MODULE;
ci->rcdev.ops = &ci_hdrc_msm_reset_ops;
ci->rcdev.of_node = pdev->dev.of_node;
ci->rcdev.nr_resets = 2;
ret = devm_reset_controller_register(&pdev->dev, &ci->rcdev);
if (ret)
return ret;
ret = clk_prepare_enable(ci->fs_clk);
if (ret)
return ret;
reset_control_assert(reset);
usleep_range(10000, 12000);
reset_control_deassert(reset);
clk_disable_unprepare(ci->fs_clk);
ret = clk_prepare_enable(ci->core_clk);
if (ret)
return ret;
ret = clk_prepare_enable(ci->iface_clk);
if (ret)
goto err_iface;
ret = ci_hdrc_msm_mux_phy(ci, pdev);
if (ret)
goto err_mux;
ulpi_node = of_get_child_by_name(pdev->dev.of_node, "ulpi");
if (ulpi_node) {
phy_node = of_get_next_available_child(ulpi_node, NULL);
ci->hsic = of_device_is_compatible(phy_node, "qcom,usb-hsic-phy");
of_node_put(phy_node);
}
of_node_put(ulpi_node);
plat_ci = ci_hdrc_add_device(&pdev->dev, pdev->resource,
pdev->num_resources, &ci->pdata);
if (IS_ERR(plat_ci)) {
ret = PTR_ERR(plat_ci);
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n");
goto err_mux;
}
ci->ci = plat_ci;
pm_runtime_set_active(&pdev->dev);
pm_runtime_no_callbacks(&pdev->dev);
pm_runtime_enable(&pdev->dev);
return 0;
err_mux:
clk_disable_unprepare(ci->iface_clk);
err_iface:
clk_disable_unprepare(ci->core_clk);
return ret;
}
static void ci_hdrc_msm_remove(struct platform_device *pdev)
{
struct ci_hdrc_msm *ci = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
ci_hdrc_remove_device(ci->ci);
clk_disable_unprepare(ci->iface_clk);
clk_disable_unprepare(ci->core_clk);
}
static const struct of_device_id msm_ci_dt_match[] = {
{ .compatible = "qcom,ci-hdrc", },
{ }
};
MODULE_DEVICE_TABLE(of, msm_ci_dt_match);
static struct platform_driver ci_hdrc_msm_driver = {
.probe = ci_hdrc_msm_probe,
.remove = ci_hdrc_msm_remove,
.driver = {
.name = "msm_hsusb",
.of_match_table = msm_ci_dt_match,
},
};
module_platform_driver(ci_hdrc_msm_driver);
MODULE_ALIAS("platform:msm_hsusb");
MODULE_ALIAS("platform:ci13xxx_msm");
MODULE_DESCRIPTION("ChipIdea Highspeed Dual Role Controller");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,112 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2023 Nuvoton Technology corporation.
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/usb/chipidea.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/reset-controller.h>
#include <linux/of.h>
#include "ci.h"
struct npcm_udc_data {
struct platform_device *ci;
struct clk *core_clk;
struct ci_hdrc_platform_data pdata;
};
static int npcm_udc_notify_event(struct ci_hdrc *ci, unsigned int event)
{
struct device *dev = ci->dev->parent;
switch (event) {
case CI_HDRC_CONTROLLER_RESET_EVENT:
/* clear all mode bits */
hw_write(ci, OP_USBMODE, 0xffffffff, 0x0);
break;
default:
dev_dbg(dev, "unknown ci_hdrc event (%d)\n", event);
break;
}
return 0;
}
static int npcm_udc_probe(struct platform_device *pdev)
{
int ret;
struct npcm_udc_data *ci;
struct platform_device *plat_ci;
struct device *dev = &pdev->dev;
ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL);
if (!ci)
return -ENOMEM;
platform_set_drvdata(pdev, ci);
ci->core_clk = devm_clk_get_optional(dev, NULL);
if (IS_ERR(ci->core_clk))
return PTR_ERR(ci->core_clk);
ret = clk_prepare_enable(ci->core_clk);
if (ret)
return dev_err_probe(dev, ret, "failed to enable the clock: %d\n", ret);
ci->pdata.name = dev_name(dev);
ci->pdata.capoffset = DEF_CAPOFFSET;
ci->pdata.flags = CI_HDRC_REQUIRES_ALIGNED_DMA |
CI_HDRC_FORCE_VBUS_ACTIVE_ALWAYS;
ci->pdata.phy_mode = USBPHY_INTERFACE_MODE_UTMI;
ci->pdata.notify_event = npcm_udc_notify_event;
plat_ci = ci_hdrc_add_device(dev, pdev->resource, pdev->num_resources,
&ci->pdata);
if (IS_ERR(plat_ci)) {
ret = PTR_ERR(plat_ci);
dev_err(dev, "failed to register HDRC NPCM device: %d\n", ret);
goto clk_err;
}
pm_runtime_no_callbacks(dev);
pm_runtime_enable(dev);
return 0;
clk_err:
clk_disable_unprepare(ci->core_clk);
return ret;
}
static void npcm_udc_remove(struct platform_device *pdev)
{
struct npcm_udc_data *ci = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
ci_hdrc_remove_device(ci->ci);
clk_disable_unprepare(ci->core_clk);
}
static const struct of_device_id npcm_udc_dt_match[] = {
{ .compatible = "nuvoton,npcm750-udc", },
{ .compatible = "nuvoton,npcm845-udc", },
{ }
};
MODULE_DEVICE_TABLE(of, npcm_udc_dt_match);
static struct platform_driver npcm_udc_driver = {
.probe = npcm_udc_probe,
.remove = npcm_udc_remove,
.driver = {
.name = "npcm_udc",
.of_match_table = npcm_udc_dt_match,
},
};
module_platform_driver(npcm_udc_driver);
MODULE_DESCRIPTION("NPCM USB device controller driver");
MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,170 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ci_hdrc_pci.c - MIPS USB IP core family device controller
*
* Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved.
*
* Author: David Lopo
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/usb/gadget.h>
#include <linux/usb/chipidea.h>
#include <linux/usb/usb_phy_generic.h>
/* driver name */
#define UDC_DRIVER_NAME "ci_hdrc_pci"
struct ci_hdrc_pci {
struct platform_device *ci;
struct platform_device *phy;
};
/******************************************************************************
* PCI block
*****************************************************************************/
static struct ci_hdrc_platform_data pci_platdata = {
.name = UDC_DRIVER_NAME,
.capoffset = DEF_CAPOFFSET,
};
static struct ci_hdrc_platform_data langwell_pci_platdata = {
.name = UDC_DRIVER_NAME,
.capoffset = 0,
};
static struct ci_hdrc_platform_data penwell_pci_platdata = {
.name = UDC_DRIVER_NAME,
.capoffset = 0,
.power_budget = 200,
};
/**
* ci_hdrc_pci_probe: PCI probe
* @pdev: USB device controller being probed
* @id: PCI hotplug ID connecting controller to UDC framework
*
* This function returns an error code
* Allocates basic PCI resources for this USB device controller, and then
* invokes the udc_probe() method to start the UDC associated with it
*/
static int ci_hdrc_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct ci_hdrc_platform_data *platdata = (void *)id->driver_data;
struct ci_hdrc_pci *ci;
struct resource res[3];
int retval = 0, nres = 2;
if (!platdata) {
dev_err(&pdev->dev, "device doesn't provide driver data\n");
return -ENODEV;
}
ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL);
if (!ci)
return -ENOMEM;
retval = pcim_enable_device(pdev);
if (retval)
return retval;
if (!pdev->irq) {
dev_err(&pdev->dev, "No IRQ, check BIOS/PCI setup!");
return -ENODEV;
}
pci_set_master(pdev);
pci_try_set_mwi(pdev);
/* register a nop PHY */
ci->phy = usb_phy_generic_register();
if (IS_ERR(ci->phy))
return PTR_ERR(ci->phy);
memset(res, 0, sizeof(res));
res[0].start = pci_resource_start(pdev, 0);
res[0].end = pci_resource_end(pdev, 0);
res[0].flags = IORESOURCE_MEM;
res[1].start = pdev->irq;
res[1].flags = IORESOURCE_IRQ;
ci->ci = ci_hdrc_add_device(&pdev->dev, res, nres, platdata);
if (IS_ERR(ci->ci)) {
dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n");
usb_phy_generic_unregister(ci->phy);
return PTR_ERR(ci->ci);
}
pci_set_drvdata(pdev, ci);
return 0;
}
/**
* ci_hdrc_pci_remove: PCI remove
* @pdev: USB Device Controller being removed
*
* Reverses the effect of ci_hdrc_pci_probe(),
* first invoking the udc_remove() and then releases
* all PCI resources allocated for this USB device controller
*/
static void ci_hdrc_pci_remove(struct pci_dev *pdev)
{
struct ci_hdrc_pci *ci = pci_get_drvdata(pdev);
ci_hdrc_remove_device(ci->ci);
usb_phy_generic_unregister(ci->phy);
}
/*
* PCI device table
* PCI device structure
*
* Check "pci.h" for details
*
* Note: ehci-pci driver may try to probe the device first. You have to add an
* ID to the bypass_pci_id_table in ehci-pci driver to prevent this.
*/
static const struct pci_device_id ci_hdrc_pci_id_table[] = {
{
PCI_DEVICE(0x153F, 0x1004),
.driver_data = (kernel_ulong_t)&pci_platdata,
},
{
PCI_DEVICE(0x153F, 0x1006),
.driver_data = (kernel_ulong_t)&pci_platdata,
},
{
PCI_VDEVICE(INTEL, 0x0811),
.driver_data = (kernel_ulong_t)&langwell_pci_platdata,
},
{
PCI_VDEVICE(INTEL, 0x0829),
.driver_data = (kernel_ulong_t)&penwell_pci_platdata,
},
{
/* Intel Clovertrail */
PCI_VDEVICE(INTEL, 0xe006),
.driver_data = (kernel_ulong_t)&penwell_pci_platdata,
},
{ 0 } /* end: all zeroes */
};
MODULE_DEVICE_TABLE(pci, ci_hdrc_pci_id_table);
static struct pci_driver ci_hdrc_pci_driver = {
.name = UDC_DRIVER_NAME,
.id_table = ci_hdrc_pci_id_table,
.probe = ci_hdrc_pci_probe,
.remove = ci_hdrc_pci_remove,
};
module_pci_driver(ci_hdrc_pci_driver);
MODULE_AUTHOR("MIPS - David Lopo <dlopo@chipidea.mips.com>");
MODULE_DESCRIPTION("MIPS CI13XXX USB Peripheral Controller");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:ci13xxx_pci");
@@ -0,0 +1,415 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2016, NVIDIA Corporation
*/
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/usb.h>
#include <linux/usb/chipidea.h>
#include <linux/usb/hcd.h>
#include <linux/usb/of.h>
#include <linux/usb/phy.h>
#include <soc/tegra/common.h>
#include "../host/ehci.h"
#include "ci.h"
struct tegra_usb {
struct ci_hdrc_platform_data data;
struct platform_device *dev;
const struct tegra_usb_soc_info *soc;
struct usb_phy *phy;
struct clk *clk;
bool needs_double_reset;
};
struct tegra_usb_soc_info {
unsigned long flags;
unsigned int txfifothresh;
enum usb_dr_mode dr_mode;
};
static const struct tegra_usb_soc_info tegra20_ehci_soc_info = {
.flags = CI_HDRC_REQUIRES_ALIGNED_DMA |
CI_HDRC_OVERRIDE_PHY_CONTROL |
CI_HDRC_SUPPORTS_RUNTIME_PM,
.dr_mode = USB_DR_MODE_HOST,
.txfifothresh = 10,
};
static const struct tegra_usb_soc_info tegra30_ehci_soc_info = {
.flags = CI_HDRC_REQUIRES_ALIGNED_DMA |
CI_HDRC_OVERRIDE_PHY_CONTROL |
CI_HDRC_SUPPORTS_RUNTIME_PM,
.dr_mode = USB_DR_MODE_HOST,
.txfifothresh = 16,
};
static const struct tegra_usb_soc_info tegra20_udc_soc_info = {
.flags = CI_HDRC_REQUIRES_ALIGNED_DMA |
CI_HDRC_OVERRIDE_PHY_CONTROL |
CI_HDRC_SUPPORTS_RUNTIME_PM,
.dr_mode = USB_DR_MODE_UNKNOWN,
.txfifothresh = 10,
};
static const struct tegra_usb_soc_info tegra30_udc_soc_info = {
.flags = CI_HDRC_REQUIRES_ALIGNED_DMA |
CI_HDRC_OVERRIDE_PHY_CONTROL |
CI_HDRC_SUPPORTS_RUNTIME_PM,
.dr_mode = USB_DR_MODE_UNKNOWN,
.txfifothresh = 16,
};
static const struct of_device_id tegra_usb_of_match[] = {
{
.compatible = "nvidia,tegra20-ehci",
.data = &tegra20_ehci_soc_info,
}, {
.compatible = "nvidia,tegra30-ehci",
.data = &tegra30_ehci_soc_info,
}, {
.compatible = "nvidia,tegra20-udc",
.data = &tegra20_udc_soc_info,
}, {
.compatible = "nvidia,tegra30-udc",
.data = &tegra30_udc_soc_info,
}, {
.compatible = "nvidia,tegra114-udc",
.data = &tegra30_udc_soc_info,
}, {
.compatible = "nvidia,tegra124-udc",
.data = &tegra30_udc_soc_info,
}, {
/* sentinel */
}
};
MODULE_DEVICE_TABLE(of, tegra_usb_of_match);
static int tegra_usb_reset_controller(struct device *dev)
{
struct reset_control *rst, *rst_utmi;
struct device_node *phy_np;
int err;
rst = devm_reset_control_get_shared(dev, "usb");
if (IS_ERR(rst)) {
dev_err(dev, "can't get ehci reset: %pe\n", rst);
return PTR_ERR(rst);
}
phy_np = of_parse_phandle(dev->of_node, "nvidia,phy", 0);
if (!phy_np)
return -ENOENT;
/*
* The 1st USB controller contains some UTMI pad registers that are
* global for all the controllers on the chip. Those registers are
* also cleared when reset is asserted to the 1st controller.
*/
rst_utmi = of_reset_control_get_shared(phy_np, "utmi-pads");
if (IS_ERR(rst_utmi)) {
dev_warn(dev, "can't get utmi-pads reset from the PHY\n");
dev_warn(dev, "continuing, but please update your DT\n");
} else {
/*
* PHY driver performs UTMI-pads reset in a case of a
* non-legacy DT.
*/
reset_control_put(rst_utmi);
}
of_node_put(phy_np);
/* reset control is shared, hence initialize it first */
err = reset_control_deassert(rst);
if (err)
return err;
err = reset_control_assert(rst);
if (err)
return err;
udelay(1);
err = reset_control_deassert(rst);
if (err)
return err;
return 0;
}
static int tegra_usb_notify_event(struct ci_hdrc *ci, unsigned int event)
{
struct tegra_usb *usb = dev_get_drvdata(ci->dev->parent);
struct ehci_hcd *ehci;
switch (event) {
case CI_HDRC_CONTROLLER_RESET_EVENT:
if (ci->hcd) {
ehci = hcd_to_ehci(ci->hcd);
ehci->has_tdi_phy_lpm = false;
ehci_writel(ehci, usb->soc->txfifothresh << 16,
&ehci->regs->txfill_tuning);
}
break;
}
return 0;
}
static int tegra_usb_internal_port_reset(struct ehci_hcd *ehci,
u32 __iomem *portsc_reg,
unsigned long *flags)
{
u32 saved_usbintr, temp;
unsigned int i, tries;
int retval = 0;
saved_usbintr = ehci_readl(ehci, &ehci->regs->intr_enable);
/* disable USB interrupt */
ehci_writel(ehci, 0, &ehci->regs->intr_enable);
spin_unlock_irqrestore(&ehci->lock, *flags);
/*
* Here we have to do Port Reset at most twice for
* Port Enable bit to be set.
*/
for (i = 0; i < 2; i++) {
temp = ehci_readl(ehci, portsc_reg);
temp |= PORT_RESET;
ehci_writel(ehci, temp, portsc_reg);
fsleep(10000);
temp &= ~PORT_RESET;
ehci_writel(ehci, temp, portsc_reg);
fsleep(1000);
tries = 100;
do {
fsleep(1000);
/*
* Up to this point, Port Enable bit is
* expected to be set after 2 ms waiting.
* USB1 usually takes extra 45 ms, for safety,
* we take 100 ms as timeout.
*/
temp = ehci_readl(ehci, portsc_reg);
} while (!(temp & PORT_PE) && tries--);
if (temp & PORT_PE)
break;
}
if (i == 2)
retval = -ETIMEDOUT;
/*
* Clear Connect Status Change bit if it's set.
* We can't clear PORT_PEC. It will also cause PORT_PE to be cleared.
*/
if (temp & PORT_CSC)
ehci_writel(ehci, PORT_CSC, portsc_reg);
/*
* Write to clear any interrupt status bits that might be set
* during port reset.
*/
temp = ehci_readl(ehci, &ehci->regs->status);
ehci_writel(ehci, temp, &ehci->regs->status);
/* restore original interrupt-enable bits */
spin_lock_irqsave(&ehci->lock, *flags);
ehci_writel(ehci, saved_usbintr, &ehci->regs->intr_enable);
return retval;
}
static int tegra_ehci_hub_control(struct ci_hdrc *ci, u16 typeReq, u16 wValue,
u16 wIndex, char *buf, u16 wLength,
bool *done, unsigned long *flags)
{
struct tegra_usb *usb = dev_get_drvdata(ci->dev->parent);
struct ehci_hcd *ehci = hcd_to_ehci(ci->hcd);
u32 __iomem *status_reg;
int retval = 0;
status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1];
switch (typeReq) {
case SetPortFeature:
if (wValue != USB_PORT_FEAT_RESET || !usb->needs_double_reset)
break;
/* for USB1 port we need to issue Port Reset twice internally */
retval = tegra_usb_internal_port_reset(ehci, status_reg, flags);
*done = true;
break;
}
return retval;
}
static void tegra_usb_enter_lpm(struct ci_hdrc *ci, bool enable)
{
/*
* Touching any register which belongs to AHB clock domain will
* hang CPU if USB controller is put into low power mode because
* AHB USB clock is gated on Tegra in the LPM.
*
* Tegra PHY has a separate register for checking the clock status
* and usb_phy_set_suspend() takes care of gating/ungating the clocks
* and restoring the PHY state on Tegra. Hence DEVLC/PORTSC registers
* shouldn't be touched directly by the CI driver.
*/
usb_phy_set_suspend(ci->usb_phy, enable);
}
static int tegra_usb_probe(struct platform_device *pdev)
{
const struct tegra_usb_soc_info *soc;
struct tegra_usb *usb;
int err;
usb = devm_kzalloc(&pdev->dev, sizeof(*usb), GFP_KERNEL);
if (!usb)
return -ENOMEM;
platform_set_drvdata(pdev, usb);
soc = of_device_get_match_data(&pdev->dev);
if (!soc) {
dev_err(&pdev->dev, "failed to match OF data\n");
return -EINVAL;
}
usb->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "nvidia,phy", 0);
if (IS_ERR(usb->phy))
return dev_err_probe(&pdev->dev, PTR_ERR(usb->phy),
"failed to get PHY");
usb->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(usb->clk))
return dev_err_probe(&pdev->dev, PTR_ERR(usb->clk),
"failed to get clock");
err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
if (err)
return err;
pm_runtime_enable(&pdev->dev);
err = pm_runtime_resume_and_get(&pdev->dev);
if (err)
return err;
if (device_property_present(&pdev->dev, "nvidia,needs-double-reset"))
usb->needs_double_reset = true;
err = tegra_usb_reset_controller(&pdev->dev);
if (err) {
dev_err_probe(&pdev->dev, err, "failed to reset controller");
goto fail_power_off;
}
/*
* USB controller registers shouldn't be touched before PHY is
* initialized, otherwise CPU will hang because clocks are gated.
* PHY driver controls gating of internal USB clocks on Tegra.
*/
err = usb_phy_init(usb->phy);
if (err)
goto fail_power_off;
/* setup and register ChipIdea HDRC device */
usb->soc = soc;
usb->data.name = "tegra-usb";
usb->data.flags = soc->flags;
usb->data.usb_phy = usb->phy;
usb->data.dr_mode = soc->dr_mode;
usb->data.capoffset = DEF_CAPOFFSET;
usb->data.enter_lpm = tegra_usb_enter_lpm;
usb->data.hub_control = tegra_ehci_hub_control;
usb->data.notify_event = tegra_usb_notify_event;
/* Tegra PHY driver currently doesn't support LPM for ULPI */
if (of_usb_get_phy_mode(pdev->dev.of_node) == USBPHY_INTERFACE_MODE_ULPI)
usb->data.flags &= ~CI_HDRC_SUPPORTS_RUNTIME_PM;
usb->dev = ci_hdrc_add_device(&pdev->dev, pdev->resource,
pdev->num_resources, &usb->data);
if (IS_ERR(usb->dev)) {
err = dev_err_probe(&pdev->dev, PTR_ERR(usb->dev),
"failed to add HDRC device");
goto phy_shutdown;
}
return 0;
phy_shutdown:
usb_phy_shutdown(usb->phy);
fail_power_off:
pm_runtime_put_sync_suspend(&pdev->dev);
pm_runtime_force_suspend(&pdev->dev);
return err;
}
static void tegra_usb_remove(struct platform_device *pdev)
{
struct tegra_usb *usb = platform_get_drvdata(pdev);
ci_hdrc_remove_device(usb->dev);
usb_phy_shutdown(usb->phy);
pm_runtime_put_sync_suspend(&pdev->dev);
pm_runtime_force_suspend(&pdev->dev);
}
static int tegra_usb_runtime_resume(struct device *dev)
{
struct tegra_usb *usb = dev_get_drvdata(dev);
int err;
err = clk_prepare_enable(usb->clk);
if (err < 0) {
dev_err(dev, "failed to enable clock: %d\n", err);
return err;
}
return 0;
}
static int tegra_usb_runtime_suspend(struct device *dev)
{
struct tegra_usb *usb = dev_get_drvdata(dev);
clk_disable_unprepare(usb->clk);
return 0;
}
static const struct dev_pm_ops tegra_usb_pm = {
RUNTIME_PM_OPS(tegra_usb_runtime_suspend, tegra_usb_runtime_resume, NULL)
};
static struct platform_driver tegra_usb_driver = {
.driver = {
.name = "tegra-usb",
.of_match_table = tegra_usb_of_match,
.pm = pm_ptr(&tegra_usb_pm),
},
.probe = tegra_usb_probe,
.remove = tegra_usb_remove,
};
module_platform_driver(tegra_usb_driver);
MODULE_DESCRIPTION("NVIDIA Tegra USB driver");
MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,129 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2014 Marvell Technology Group Ltd.
*
* Antoine Tenart <antoine.tenart@free-electrons.com>
*/
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/usb/chipidea.h>
#include <linux/usb/hcd.h>
#include <linux/usb/ulpi.h>
#include "ci.h"
struct ci_hdrc_usb2_priv {
struct platform_device *ci_pdev;
struct clk *clk;
};
static const struct ci_hdrc_platform_data ci_default_pdata = {
.capoffset = DEF_CAPOFFSET,
.flags = CI_HDRC_DISABLE_STREAMING,
};
static const struct ci_hdrc_platform_data ci_zynq_pdata = {
.capoffset = DEF_CAPOFFSET,
.flags = CI_HDRC_PHY_VBUS_CONTROL,
};
static const struct ci_hdrc_platform_data ci_zevio_pdata = {
.capoffset = DEF_CAPOFFSET,
.flags = CI_HDRC_REGS_SHARED | CI_HDRC_FORCE_FULLSPEED,
};
static const struct of_device_id ci_hdrc_usb2_of_match[] = {
{ .compatible = "chipidea,usb2" },
{ .compatible = "xlnx,zynq-usb-2.20a", .data = &ci_zynq_pdata },
{ .compatible = "lsi,zevio-usb", .data = &ci_zevio_pdata },
{ }
};
MODULE_DEVICE_TABLE(of, ci_hdrc_usb2_of_match);
static int ci_hdrc_usb2_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ci_hdrc_usb2_priv *priv;
struct ci_hdrc_platform_data *ci_pdata = dev_get_platdata(dev);
const struct ci_hdrc_platform_data *data;
int ret;
if (!ci_pdata) {
ci_pdata = devm_kmalloc(dev, sizeof(*ci_pdata), GFP_KERNEL);
if (!ci_pdata)
return -ENOMEM;
*ci_pdata = ci_default_pdata; /* struct copy */
}
data = device_get_match_data(&pdev->dev);
if (data)
/* struct copy */
*ci_pdata = *data;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->clk = devm_clk_get_optional(dev, NULL);
if (IS_ERR(priv->clk))
return PTR_ERR(priv->clk);
ret = clk_prepare_enable(priv->clk);
if (ret) {
dev_err(dev, "failed to enable the clock: %d\n", ret);
return ret;
}
ci_pdata->name = dev_name(dev);
priv->ci_pdev = ci_hdrc_add_device(dev, pdev->resource,
pdev->num_resources, ci_pdata);
if (IS_ERR(priv->ci_pdev)) {
ret = PTR_ERR(priv->ci_pdev);
if (ret != -EPROBE_DEFER)
dev_err(dev,
"failed to register ci_hdrc platform device: %d\n",
ret);
goto clk_err;
}
platform_set_drvdata(pdev, priv);
pm_runtime_no_callbacks(dev);
pm_runtime_enable(dev);
return 0;
clk_err:
clk_disable_unprepare(priv->clk);
return ret;
}
static void ci_hdrc_usb2_remove(struct platform_device *pdev)
{
struct ci_hdrc_usb2_priv *priv = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
ci_hdrc_remove_device(priv->ci_pdev);
clk_disable_unprepare(priv->clk);
}
static struct platform_driver ci_hdrc_usb2_driver = {
.probe = ci_hdrc_usb2_probe,
.remove = ci_hdrc_usb2_remove,
.driver = {
.name = "chipidea-usb2",
.of_match_table = ci_hdrc_usb2_of_match,
},
};
module_platform_driver(ci_hdrc_usb2_driver);
MODULE_DESCRIPTION("ChipIdea HDRC USB2 binding for ci13xxx");
MODULE_AUTHOR("Antoine Tenart <antoine.tenart@free-electrons.com>");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
+313
View File
@@ -0,0 +1,313 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/phy.h>
#include <linux/usb/otg.h>
#include <linux/usb/otg-fsm.h>
#include <linux/usb/chipidea.h>
#include "ci.h"
#include "udc.h"
#include "bits.h"
#include "otg.h"
/*
* ci_device_show: prints information about device capabilities and status
*/
static int ci_device_show(struct seq_file *s, void *data)
{
struct ci_hdrc *ci = s->private;
struct usb_gadget *gadget = &ci->gadget;
seq_printf(s, "speed = %d\n", gadget->speed);
seq_printf(s, "max_speed = %d\n", gadget->max_speed);
seq_printf(s, "is_otg = %d\n", gadget->is_otg);
seq_printf(s, "is_a_peripheral = %d\n", gadget->is_a_peripheral);
seq_printf(s, "b_hnp_enable = %d\n", gadget->b_hnp_enable);
seq_printf(s, "a_hnp_support = %d\n", gadget->a_hnp_support);
seq_printf(s, "a_alt_hnp_support = %d\n", gadget->a_alt_hnp_support);
seq_printf(s, "name = %s\n",
(gadget->name ? gadget->name : ""));
if (!ci->driver)
return 0;
seq_printf(s, "gadget function = %s\n",
(ci->driver->function ? ci->driver->function : ""));
seq_printf(s, "gadget max speed = %d\n", ci->driver->max_speed);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(ci_device);
/*
* ci_port_test_show: reads port test mode
*/
static int ci_port_test_show(struct seq_file *s, void *data)
{
struct ci_hdrc *ci = s->private;
unsigned long flags;
unsigned mode;
pm_runtime_get_sync(ci->dev);
spin_lock_irqsave(&ci->lock, flags);
mode = hw_port_test_get(ci);
spin_unlock_irqrestore(&ci->lock, flags);
pm_runtime_put_sync(ci->dev);
seq_printf(s, "mode = %u\n", mode);
return 0;
}
/*
* ci_port_test_write: writes port test mode
*/
static ssize_t ci_port_test_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
struct ci_hdrc *ci = s->private;
unsigned long flags;
unsigned mode;
char buf[32];
int ret;
count = min_t(size_t, sizeof(buf) - 1, count);
if (copy_from_user(buf, ubuf, count))
return -EFAULT;
/* sscanf requires a zero terminated string */
buf[count] = '\0';
if (sscanf(buf, "%u", &mode) != 1)
return -EINVAL;
if (mode > 255)
return -EBADRQC;
pm_runtime_get_sync(ci->dev);
spin_lock_irqsave(&ci->lock, flags);
ret = hw_port_test_set(ci, mode);
spin_unlock_irqrestore(&ci->lock, flags);
pm_runtime_put_sync(ci->dev);
return ret ? ret : count;
}
static int ci_port_test_open(struct inode *inode, struct file *file)
{
return single_open(file, ci_port_test_show, inode->i_private);
}
static const struct file_operations ci_port_test_fops = {
.open = ci_port_test_open,
.write = ci_port_test_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
/*
* ci_qheads_show: DMA contents of all queue heads
*/
static int ci_qheads_show(struct seq_file *s, void *data)
{
struct ci_hdrc *ci = s->private;
unsigned long flags;
unsigned i, j;
if (ci->role != CI_ROLE_GADGET) {
seq_printf(s, "not in gadget mode\n");
return 0;
}
spin_lock_irqsave(&ci->lock, flags);
for (i = 0; i < ci->hw_ep_max/2; i++) {
struct ci_hw_ep *hweprx = &ci->ci_hw_ep[i];
struct ci_hw_ep *hweptx =
&ci->ci_hw_ep[i + ci->hw_ep_max/2];
seq_printf(s, "EP=%02i: RX=%08X TX=%08X\n",
i, (u32)hweprx->qh.dma, (u32)hweptx->qh.dma);
for (j = 0; j < (sizeof(struct ci_hw_qh)/sizeof(u32)); j++)
seq_printf(s, " %04X: %08X %08X\n", j,
*((u32 *)hweprx->qh.ptr + j),
*((u32 *)hweptx->qh.ptr + j));
}
spin_unlock_irqrestore(&ci->lock, flags);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(ci_qheads);
/*
* ci_requests_show: DMA contents of all requests currently queued (all endpts)
*/
static int ci_requests_show(struct seq_file *s, void *data)
{
struct ci_hdrc *ci = s->private;
unsigned long flags;
struct ci_hw_req *req = NULL;
struct td_node *node, *tmpnode;
unsigned i, j, qsize = sizeof(struct ci_hw_td)/sizeof(u32);
if (ci->role != CI_ROLE_GADGET) {
seq_printf(s, "not in gadget mode\n");
return 0;
}
spin_lock_irqsave(&ci->lock, flags);
for (i = 0; i < ci->hw_ep_max; i++)
list_for_each_entry(req, &ci->ci_hw_ep[i].qh.queue, queue) {
list_for_each_entry_safe(node, tmpnode, &req->tds, td) {
seq_printf(s, "EP=%02i: TD=%08X %s\n",
i % (ci->hw_ep_max / 2),
(u32)node->dma,
((i < ci->hw_ep_max/2) ?
"RX" : "TX"));
for (j = 0; j < qsize; j++)
seq_printf(s, " %04X: %08X\n", j,
*((u32 *)node->ptr + j));
}
}
spin_unlock_irqrestore(&ci->lock, flags);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(ci_requests);
static int ci_otg_show(struct seq_file *s, void *unused)
{
struct ci_hdrc *ci = s->private;
struct otg_fsm *fsm;
if (!ci || !ci_otg_is_fsm_mode(ci))
return 0;
fsm = &ci->fsm;
/* ------ State ----- */
seq_printf(s, "OTG state: %s\n\n",
usb_otg_state_string(ci->otg.state));
/* ------ State Machine Variables ----- */
seq_printf(s, "a_bus_drop: %d\n", fsm->a_bus_drop);
seq_printf(s, "a_bus_req: %d\n", fsm->a_bus_req);
seq_printf(s, "a_srp_det: %d\n", fsm->a_srp_det);
seq_printf(s, "a_vbus_vld: %d\n", fsm->a_vbus_vld);
seq_printf(s, "b_conn: %d\n", fsm->b_conn);
seq_printf(s, "adp_change: %d\n", fsm->adp_change);
seq_printf(s, "power_up: %d\n", fsm->power_up);
seq_printf(s, "a_bus_resume: %d\n", fsm->a_bus_resume);
seq_printf(s, "a_bus_suspend: %d\n", fsm->a_bus_suspend);
seq_printf(s, "a_conn: %d\n", fsm->a_conn);
seq_printf(s, "b_bus_req: %d\n", fsm->b_bus_req);
seq_printf(s, "b_bus_suspend: %d\n", fsm->b_bus_suspend);
seq_printf(s, "b_se0_srp: %d\n", fsm->b_se0_srp);
seq_printf(s, "b_ssend_srp: %d\n", fsm->b_ssend_srp);
seq_printf(s, "b_sess_vld: %d\n", fsm->b_sess_vld);
seq_printf(s, "b_srp_done: %d\n", fsm->b_srp_done);
seq_printf(s, "drv_vbus: %d\n", fsm->drv_vbus);
seq_printf(s, "loc_conn: %d\n", fsm->loc_conn);
seq_printf(s, "loc_sof: %d\n", fsm->loc_sof);
seq_printf(s, "adp_prb: %d\n", fsm->adp_prb);
seq_printf(s, "id: %d\n", fsm->id);
seq_printf(s, "protocol: %d\n", fsm->protocol);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(ci_otg);
static int ci_registers_show(struct seq_file *s, void *unused)
{
struct ci_hdrc *ci = s->private;
u32 tmp_reg;
if (!ci || ci->in_lpm)
return -EPERM;
/* ------ Registers ----- */
tmp_reg = hw_read_intr_enable(ci);
seq_printf(s, "USBINTR reg: %08x\n", tmp_reg);
tmp_reg = hw_read_intr_status(ci);
seq_printf(s, "USBSTS reg: %08x\n", tmp_reg);
tmp_reg = hw_read(ci, OP_USBMODE, ~0);
seq_printf(s, "USBMODE reg: %08x\n", tmp_reg);
tmp_reg = hw_read(ci, OP_USBCMD, ~0);
seq_printf(s, "USBCMD reg: %08x\n", tmp_reg);
tmp_reg = hw_read(ci, OP_PORTSC, ~0);
seq_printf(s, "PORTSC reg: %08x\n", tmp_reg);
if (ci->is_otg) {
tmp_reg = hw_read_otgsc(ci, ~0);
seq_printf(s, "OTGSC reg: %08x\n", tmp_reg);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(ci_registers);
/**
* dbg_create_files: initializes the attribute interface
* @ci: device
*
* This function returns an error code
*/
void dbg_create_files(struct ci_hdrc *ci)
{
struct dentry *dir;
dir = debugfs_create_dir(dev_name(ci->dev), usb_debug_root);
debugfs_create_file("device", S_IRUGO, dir, ci, &ci_device_fops);
debugfs_create_file("port_test", S_IRUGO | S_IWUSR, dir, ci, &ci_port_test_fops);
debugfs_create_file("qheads", S_IRUGO, dir, ci, &ci_qheads_fops);
debugfs_create_file("requests", S_IRUGO, dir, ci, &ci_requests_fops);
if (ci_otg_is_fsm_mode(ci))
debugfs_create_file("otg", S_IRUGO, dir, ci, &ci_otg_fops);
debugfs_create_file("registers", S_IRUGO, dir, ci, &ci_registers_fops);
}
/**
* dbg_remove_files: destroys the attribute interface
* @ci: device
*/
void dbg_remove_files(struct ci_hdrc *ci)
{
debugfs_lookup_and_remove(dev_name(ci->dev), usb_debug_root);
}
+509
View File
@@ -0,0 +1,509 @@
// SPDX-License-Identifier: GPL-2.0
/*
* host.c - ChipIdea USB host controller driver
*
* Copyright (c) 2012 Intel Corporation
*
* Author: Alexander Shishkin
*/
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/usb.h>
#include <linux/usb/hcd.h>
#include <linux/usb/chipidea.h>
#include <linux/regulator/consumer.h>
#include <linux/string_choices.h>
#include <linux/pinctrl/consumer.h>
#include "../host/ehci.h"
#include "ci.h"
#include "bits.h"
#include "host.h"
static struct hc_driver __read_mostly ci_ehci_hc_driver;
static int (*orig_bus_suspend)(struct usb_hcd *hcd);
struct ehci_ci_priv {
struct regulator *reg_vbus;
bool enabled;
};
struct ci_hdrc_dma_aligned_buffer {
void *original_buffer;
u8 data[];
};
static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable)
{
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
struct ehci_ci_priv *priv = (struct ehci_ci_priv *)ehci->priv;
struct device *dev = hcd->self.controller;
struct ci_hdrc *ci = dev_get_drvdata(dev);
int ret = 0;
int port = HCS_N_PORTS(ehci->hcs_params);
if (priv->reg_vbus && enable != priv->enabled) {
if (port > 1) {
dev_warn(dev,
"Not support multi-port regulator control\n");
return 0;
}
if (enable)
ret = regulator_enable(priv->reg_vbus);
else
ret = regulator_disable(priv->reg_vbus);
if (ret) {
dev_err(dev,
"Failed to %s vbus regulator, ret=%d\n",
str_enable_disable(enable), ret);
return ret;
}
priv->enabled = enable;
}
if (ci->platdata->flags & CI_HDRC_PHY_VBUS_CONTROL) {
if (enable)
usb_phy_vbus_on(ci->usb_phy);
else
usb_phy_vbus_off(ci->usb_phy);
}
if (enable && (ci->platdata->phy_mode == USBPHY_INTERFACE_MODE_HSIC)) {
/*
* Marvell 28nm HSIC PHY requires forcing the port to HS mode.
* As HSIC is always HS, this should be safe for others.
*/
hw_port_test_set(ci, 5);
hw_port_test_set(ci, 0);
}
return 0;
};
static int ehci_ci_reset(struct usb_hcd *hcd)
{
struct device *dev = hcd->self.controller;
struct ci_hdrc *ci = dev_get_drvdata(dev);
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
int ret;
ret = ehci_setup(hcd);
if (ret)
return ret;
ehci->need_io_watchdog = 0;
if (ci->platdata->notify_event) {
ret = ci->platdata->notify_event(ci,
CI_HDRC_CONTROLLER_RESET_EVENT);
if (ret)
return ret;
}
ci_platform_configure(ci);
return ret;
}
static const struct ehci_driver_overrides ehci_ci_overrides = {
.extra_priv_size = sizeof(struct ehci_ci_priv),
.port_power = ehci_ci_portpower,
.reset = ehci_ci_reset,
};
static irqreturn_t host_irq(struct ci_hdrc *ci)
{
return usb_hcd_irq(ci->irq, ci->hcd);
}
static int host_start(struct ci_hdrc *ci)
{
struct usb_hcd *hcd;
struct ehci_hcd *ehci;
struct ehci_ci_priv *priv;
int ret;
if (usb_disabled())
return -ENODEV;
hcd = __usb_create_hcd(&ci_ehci_hc_driver, ci->dev->parent,
ci->dev, dev_name(ci->dev), NULL);
if (!hcd)
return -ENOMEM;
dev_set_drvdata(ci->dev, ci);
hcd->rsrc_start = ci->hw_bank.phys;
hcd->rsrc_len = ci->hw_bank.size;
hcd->regs = ci->hw_bank.abs;
hcd->has_tt = 1;
hcd->power_budget = ci->platdata->power_budget;
hcd->tpl_support = ci->platdata->tpl_support;
if (ci->phy || ci->usb_phy) {
hcd->skip_phy_initialization = 1;
if (ci->usb_phy)
hcd->usb_phy = ci->usb_phy;
}
ehci = hcd_to_ehci(hcd);
ehci->caps = ci->hw_bank.cap;
ehci->has_hostpc = ci->hw_bank.lpm;
ehci->has_tdi_phy_lpm = ci->hw_bank.lpm;
ehci->imx28_write_fix = ci->imx28_write_fix;
ehci->has_ci_pec_bug = ci->has_portsc_pec_bug;
priv = (struct ehci_ci_priv *)ehci->priv;
priv->reg_vbus = NULL;
if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci)) {
if (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON) {
ret = regulator_enable(ci->platdata->reg_vbus);
if (ret) {
dev_err(ci->dev,
"Failed to enable vbus regulator, ret=%d\n",
ret);
goto put_hcd;
}
} else {
priv->reg_vbus = ci->platdata->reg_vbus;
}
}
if (ci->platdata->pins_host)
pinctrl_select_state(ci->platdata->pctl,
ci->platdata->pins_host);
ci->hcd = hcd;
ret = usb_add_hcd(hcd, 0, 0);
if (ret) {
ci->hcd = NULL;
goto disable_reg;
} else {
struct usb_otg *otg = &ci->otg;
if (ci_otg_is_fsm_mode(ci)) {
otg->host = &hcd->self;
hcd->self.otg_port = 1;
}
if (ci->platdata->notify_event &&
(ci->platdata->flags & CI_HDRC_IMX_IS_HSIC))
ci->platdata->notify_event
(ci, CI_HDRC_IMX_HSIC_ACTIVE_EVENT);
}
return ret;
disable_reg:
if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci) &&
(ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON))
regulator_disable(ci->platdata->reg_vbus);
put_hcd:
usb_put_hcd(hcd);
return ret;
}
static void host_stop(struct ci_hdrc *ci)
{
struct usb_hcd *hcd = ci->hcd;
if (hcd) {
if (ci->platdata->notify_event)
ci->platdata->notify_event(ci,
CI_HDRC_CONTROLLER_STOPPED_EVENT);
usb_remove_hcd(hcd);
ci->role = CI_ROLE_END;
synchronize_irq(ci->irq);
usb_put_hcd(hcd);
if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci) &&
(ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON))
regulator_disable(ci->platdata->reg_vbus);
}
ci->hcd = NULL;
ci->otg.host = NULL;
if (ci->platdata->pins_host && ci->platdata->pins_default)
pinctrl_select_state(ci->platdata->pctl,
ci->platdata->pins_default);
}
void ci_hdrc_host_destroy(struct ci_hdrc *ci)
{
if (ci->role == CI_ROLE_HOST && ci->hcd)
host_stop(ci);
}
/* The below code is based on tegra ehci driver */
static int ci_ehci_hub_control(
struct usb_hcd *hcd,
u16 typeReq,
u16 wValue,
u16 wIndex,
char *buf,
u16 wLength
)
{
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
unsigned int ports = HCS_N_PORTS(ehci->hcs_params);
u32 __iomem *status_reg;
u32 temp, port_index;
unsigned long flags;
int retval = 0;
bool done = false;
struct device *dev = hcd->self.controller;
struct ci_hdrc *ci = dev_get_drvdata(dev);
/*
* Avoid out-of-bounds values while calculating the port index
* from wIndex. The compiler doesn't like pointers to invalid
* addresses, even if they are never used.
*/
port_index = (wIndex - 1) & 0xff;
if (port_index >= HCS_N_PORTS_MAX)
port_index = 0;
status_reg = &ehci->regs->port_status[port_index];
spin_lock_irqsave(&ehci->lock, flags);
if (ci->platdata->hub_control) {
retval = ci->platdata->hub_control(ci, typeReq, wValue, wIndex,
buf, wLength, &done, &flags);
if (done)
goto done;
}
if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) {
if (!wIndex || wIndex > ports) {
retval = -EPIPE;
goto done;
}
temp = ehci_readl(ehci, status_reg);
if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) {
retval = -EPIPE;
goto done;
}
temp &= ~(PORT_RWC_BITS | PORT_WKCONN_E);
temp |= PORT_WKDISC_E | PORT_WKOC_E;
ehci_writel(ehci, temp | PORT_SUSPEND, status_reg);
/*
* If a transaction is in progress, there may be a delay in
* suspending the port. Poll until the port is suspended.
*/
if (ehci_handshake(ehci, status_reg, PORT_SUSPEND,
PORT_SUSPEND, 5000))
ehci_err(ehci, "timeout waiting for SUSPEND\n");
if (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC) {
if (ci->platdata->notify_event)
ci->platdata->notify_event(ci,
CI_HDRC_IMX_HSIC_SUSPEND_EVENT);
temp = ehci_readl(ehci, status_reg);
temp &= ~(PORT_WKDISC_E | PORT_WKCONN_E);
ehci_writel(ehci, temp, status_reg);
}
set_bit(port_index, &ehci->suspended_ports);
goto done;
}
/*
* After resume has finished, it needs do some post resume
* operation for some SoCs.
*/
else if (typeReq == ClearPortFeature &&
wValue == USB_PORT_FEAT_C_SUSPEND) {
/* Make sure the resume has finished, it should be finished */
if (ehci_handshake(ehci, status_reg, PORT_RESUME, 0, 25000))
ehci_err(ehci, "timeout waiting for resume\n");
}
spin_unlock_irqrestore(&ehci->lock, flags);
/* Handle the hub control events here */
return ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
done:
spin_unlock_irqrestore(&ehci->lock, flags);
return retval;
}
static int ci_ehci_bus_suspend(struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
struct device *dev = hcd->self.controller;
struct ci_hdrc *ci = dev_get_drvdata(dev);
int port;
u32 tmp;
int ret = orig_bus_suspend(hcd);
if (ret)
return ret;
port = HCS_N_PORTS(ehci->hcs_params);
while (port--) {
u32 __iomem *reg = &ehci->regs->port_status[port];
u32 portsc = ehci_readl(ehci, reg);
if (portsc & PORT_CONNECT) {
/*
* For chipidea, the resume signal will be ended
* automatically, so for remote wakeup case, the
* usbcmd.rs may not be set before the resume has
* ended if other resume paths consumes too much
* time (~24ms), in that case, the SOF will not
* send out within 3ms after resume ends, then the
* high speed device will enter full speed mode.
*/
tmp = ehci_readl(ehci, &ehci->regs->command);
tmp |= CMD_RUN;
ehci_writel(ehci, tmp, &ehci->regs->command);
/*
* It needs a short delay between set RS bit and PHCD.
*/
usleep_range(150, 200);
/*
* Need to clear WKCN and WKOC for imx HSIC,
* otherwise, there will be wakeup event.
*/
if (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC) {
tmp = ehci_readl(ehci, reg);
tmp &= ~(PORT_WKDISC_E | PORT_WKCONN_E);
ehci_writel(ehci, tmp, reg);
}
break;
}
}
return 0;
}
static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb, bool copy_back)
{
struct ci_hdrc_dma_aligned_buffer *temp;
if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER))
return;
urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER;
temp = container_of(urb->transfer_buffer,
struct ci_hdrc_dma_aligned_buffer, data);
urb->transfer_buffer = temp->original_buffer;
if (copy_back && usb_urb_dir_in(urb)) {
size_t length;
if (usb_pipeisoc(urb->pipe))
length = urb->transfer_buffer_length;
else
length = urb->actual_length;
memcpy(temp->original_buffer, temp->data, length);
}
kfree(temp);
}
static int ci_hdrc_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags)
{
struct ci_hdrc_dma_aligned_buffer *temp;
if (urb->num_sgs || urb->sg || urb->transfer_buffer_length == 0)
return 0;
if (IS_ALIGNED((uintptr_t)urb->transfer_buffer, 4)
&& IS_ALIGNED(urb->transfer_buffer_length, 4))
return 0;
temp = kmalloc(sizeof(*temp) + ALIGN(urb->transfer_buffer_length, 4), mem_flags);
if (!temp)
return -ENOMEM;
if (usb_urb_dir_out(urb))
memcpy(temp->data, urb->transfer_buffer,
urb->transfer_buffer_length);
temp->original_buffer = urb->transfer_buffer;
urb->transfer_buffer = temp->data;
urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER;
return 0;
}
static int ci_hdrc_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
gfp_t mem_flags)
{
int ret;
ret = ci_hdrc_alloc_dma_aligned_buffer(urb, mem_flags);
if (ret)
return ret;
ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags);
if (ret)
ci_hdrc_free_dma_aligned_buffer(urb, false);
return ret;
}
static void ci_hdrc_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb)
{
usb_hcd_unmap_urb_for_dma(hcd, urb);
ci_hdrc_free_dma_aligned_buffer(urb, true);
}
#ifdef CONFIG_PM_SLEEP
static void ci_hdrc_host_suspend(struct ci_hdrc *ci)
{
ehci_suspend(ci->hcd, device_may_wakeup(ci->dev));
}
static void ci_hdrc_host_resume(struct ci_hdrc *ci, bool power_lost)
{
ehci_resume(ci->hcd, power_lost);
}
#endif
int ci_hdrc_host_init(struct ci_hdrc *ci)
{
struct ci_role_driver *rdrv;
if (!hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_HC))
return -ENXIO;
rdrv = devm_kzalloc(ci->dev, sizeof(struct ci_role_driver), GFP_KERNEL);
if (!rdrv)
return -ENOMEM;
rdrv->start = host_start;
rdrv->stop = host_stop;
#ifdef CONFIG_PM_SLEEP
rdrv->suspend = ci_hdrc_host_suspend;
rdrv->resume = ci_hdrc_host_resume;
#endif
rdrv->irq = host_irq;
rdrv->name = "host";
ci->roles[CI_ROLE_HOST] = rdrv;
if (ci->platdata->flags & CI_HDRC_REQUIRES_ALIGNED_DMA) {
ci_ehci_hc_driver.map_urb_for_dma = ci_hdrc_map_urb_for_dma;
ci_ehci_hc_driver.unmap_urb_for_dma = ci_hdrc_unmap_urb_for_dma;
}
return 0;
}
void ci_hdrc_host_driver_init(void)
{
ehci_init_driver(&ci_ehci_hc_driver, &ehci_ci_overrides);
orig_bus_suspend = ci_ehci_hc_driver.bus_suspend;
ci_ehci_hc_driver.bus_suspend = ci_ehci_bus_suspend;
ci_ehci_hc_driver.hub_control = ci_ehci_hub_control;
}
+30
View File
@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __DRIVERS_USB_CHIPIDEA_HOST_H
#define __DRIVERS_USB_CHIPIDEA_HOST_H
#ifdef CONFIG_USB_CHIPIDEA_HOST
int ci_hdrc_host_init(struct ci_hdrc *ci);
void ci_hdrc_host_destroy(struct ci_hdrc *ci);
void ci_hdrc_host_driver_init(void);
#else
static inline int ci_hdrc_host_init(struct ci_hdrc *ci)
{
return -ENXIO;
}
static inline void ci_hdrc_host_destroy(struct ci_hdrc *ci)
{
}
static inline void ci_hdrc_host_driver_init(void)
{
}
#endif
#endif /* __DRIVERS_USB_CHIPIDEA_HOST_H */
+275
View File
@@ -0,0 +1,275 @@
// SPDX-License-Identifier: GPL-2.0
/*
* otg.c - ChipIdea USB IP core OTG driver
*
* Copyright (C) 2013 Freescale Semiconductor, Inc.
*
* Author: Peter Chen
*/
/*
* This file mainly handles otgsc register, OTG fsm operations for HNP and SRP
* are also included.
*/
#include <linux/usb/otg.h>
#include <linux/usb/gadget.h>
#include <linux/usb/chipidea.h>
#include "ci.h"
#include "bits.h"
#include "otg.h"
#include "otg_fsm.h"
/**
* hw_read_otgsc - returns otgsc register bits value.
* @ci: the controller
* @mask: bitfield mask
*/
u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
{
struct ci_hdrc_cable *cable;
u32 val = hw_read(ci, OP_OTGSC, mask);
/*
* If using extcon framework for VBUS and/or ID signal
* detection overwrite OTGSC register value
*/
cable = &ci->platdata->vbus_extcon;
if (!IS_ERR(cable->edev) || ci->role_switch) {
if (cable->changed)
val |= OTGSC_BSVIS;
else
val &= ~OTGSC_BSVIS;
if (cable->connected)
val |= OTGSC_BSV;
else
val &= ~OTGSC_BSV;
if (cable->enabled)
val |= OTGSC_BSVIE;
else
val &= ~OTGSC_BSVIE;
}
cable = &ci->platdata->id_extcon;
if (!IS_ERR(cable->edev) || ci->role_switch) {
if (cable->changed)
val |= OTGSC_IDIS;
else
val &= ~OTGSC_IDIS;
if (cable->connected)
val &= ~OTGSC_ID; /* host */
else
val |= OTGSC_ID; /* device */
if (cable->enabled)
val |= OTGSC_IDIE;
else
val &= ~OTGSC_IDIE;
}
return val & mask;
}
/**
* hw_write_otgsc - updates target bits of OTGSC register.
* @ci: the controller
* @mask: bitfield mask
* @data: to be written
*/
void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data)
{
struct ci_hdrc_cable *cable;
cable = &ci->platdata->vbus_extcon;
if (!IS_ERR(cable->edev) || ci->role_switch) {
if (data & mask & OTGSC_BSVIS)
cable->changed = false;
/* Don't enable vbus interrupt if using external notifier */
if (data & mask & OTGSC_BSVIE) {
cable->enabled = true;
data &= ~OTGSC_BSVIE;
} else if (mask & OTGSC_BSVIE) {
cable->enabled = false;
}
}
cable = &ci->platdata->id_extcon;
if (!IS_ERR(cable->edev) || ci->role_switch) {
if (data & mask & OTGSC_IDIS)
cable->changed = false;
/* Don't enable id interrupt if using external notifier */
if (data & mask & OTGSC_IDIE) {
cable->enabled = true;
data &= ~OTGSC_IDIE;
} else if (mask & OTGSC_IDIE) {
cable->enabled = false;
}
}
hw_write(ci, OP_OTGSC, mask | OTGSC_INT_STATUS_BITS, data);
}
/**
* ci_otg_role - pick role based on ID pin state
* @ci: the controller
*/
enum ci_role ci_otg_role(struct ci_hdrc *ci)
{
enum ci_role role = hw_read_otgsc(ci, OTGSC_ID)
? CI_ROLE_GADGET
: CI_ROLE_HOST;
return role;
}
void ci_handle_vbus_change(struct ci_hdrc *ci)
{
if (ci->role != CI_ROLE_GADGET)
return;
if (!ci->is_otg) {
if (ci->platdata->flags & CI_HDRC_FORCE_VBUS_ACTIVE_ALWAYS)
usb_gadget_vbus_connect(&ci->gadget);
return;
}
if (hw_read_otgsc(ci, OTGSC_BSV) && !ci->vbus_active)
usb_gadget_vbus_connect(&ci->gadget);
else if (!hw_read_otgsc(ci, OTGSC_BSV) && ci->vbus_active)
usb_gadget_vbus_disconnect(&ci->gadget);
}
/**
* hw_wait_vbus_lower_bsv - When we switch to device mode, the vbus value
* should be lower than OTGSC_BSV before connecting
* to host.
*
* @ci: the controller
*
* This function returns an error code if timeout
*/
static int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci)
{
unsigned long elapse = jiffies + msecs_to_jiffies(5000);
u32 mask = OTGSC_BSV;
while (hw_read_otgsc(ci, mask)) {
if (time_after(jiffies, elapse)) {
dev_err(ci->dev, "timeout waiting for %08x in OTGSC\n",
mask);
return -ETIMEDOUT;
}
msleep(20);
}
return 0;
}
void ci_handle_id_switch(struct ci_hdrc *ci)
{
enum ci_role role;
mutex_lock(&ci->mutex);
role = ci_otg_role(ci);
if (role != ci->role) {
dev_dbg(ci->dev, "switching from %s to %s\n",
ci_role(ci)->name, ci->roles[role]->name);
if (ci->vbus_active && ci->role == CI_ROLE_GADGET)
/*
* vbus disconnect event is lost due to role
* switch occurs during system suspend.
*/
usb_gadget_vbus_disconnect(&ci->gadget);
ci_role_stop(ci);
if (role == CI_ROLE_GADGET && !ci->role_switch &&
IS_ERR(ci->platdata->vbus_extcon.edev))
/*
* Wait vbus lower than OTGSC_BSV before connecting
* to host. If connecting status is from an external
* connector instead of register, we don't need to
* care vbus on the board, since it will not affect
* external connector status.
*/
hw_wait_vbus_lower_bsv(ci);
ci_role_start(ci, role);
/* vbus change may have already occurred */
if (role == CI_ROLE_GADGET)
ci_handle_vbus_change(ci);
}
mutex_unlock(&ci->mutex);
}
/**
* ci_otg_work - perform otg (vbus/id) event handle
* @work: work struct
*/
static void ci_otg_work(struct work_struct *work)
{
struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) {
enable_irq(ci->irq);
return;
}
pm_runtime_get_sync(ci->dev);
if (ci->id_event) {
ci->id_event = false;
ci_handle_id_switch(ci);
}
if (ci->b_sess_valid_event) {
ci->b_sess_valid_event = false;
ci_handle_vbus_change(ci);
}
pm_runtime_put_sync(ci->dev);
enable_irq(ci->irq);
}
/**
* ci_hdrc_otg_init - initialize otg struct
* @ci: the controller
*/
int ci_hdrc_otg_init(struct ci_hdrc *ci)
{
INIT_WORK(&ci->work, ci_otg_work);
ci->wq = create_freezable_workqueue("ci_otg");
if (!ci->wq) {
dev_err(ci->dev, "can't create workqueue\n");
return -ENODEV;
}
if (ci_otg_is_fsm_mode(ci))
return ci_hdrc_otg_fsm_init(ci);
return 0;
}
/**
* ci_hdrc_otg_destroy - destroy otg struct
* @ci: the controller
*/
void ci_hdrc_otg_destroy(struct ci_hdrc *ci)
{
if (ci->wq)
destroy_workqueue(ci->wq);
/* Disable all OTG irq and clear status */
hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
OTGSC_INT_STATUS_BITS);
if (ci_otg_is_fsm_mode(ci))
ci_hdrc_otg_fsm_remove(ci);
}
+25
View File
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2013-2014 Freescale Semiconductor, Inc.
*
* Author: Peter Chen
*/
#ifndef __DRIVERS_USB_CHIPIDEA_OTG_H
#define __DRIVERS_USB_CHIPIDEA_OTG_H
u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask);
void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data);
int ci_hdrc_otg_init(struct ci_hdrc *ci);
void ci_hdrc_otg_destroy(struct ci_hdrc *ci);
enum ci_role ci_otg_role(struct ci_hdrc *ci);
void ci_handle_vbus_change(struct ci_hdrc *ci);
void ci_handle_id_switch(struct ci_hdrc *ci);
static inline void ci_otg_queue_work(struct ci_hdrc *ci)
{
disable_irq_nosync(ci->irq);
if (queue_work(ci->wq, &ci->work) == false)
enable_irq(ci->irq);
}
#endif /* __DRIVERS_USB_CHIPIDEA_OTG_H */
+851
View File
@@ -0,0 +1,851 @@
// SPDX-License-Identifier: GPL-2.0
/*
* otg_fsm.c - ChipIdea USB IP core OTG FSM driver
*
* Copyright (C) 2014 Freescale Semiconductor, Inc.
*
* Author: Jun Li
*/
/*
* This file mainly handles OTG fsm, it includes OTG fsm operations
* for HNP and SRP.
*
* TODO List
* - ADP
* - OTG test device
*/
#include <linux/usb/otg.h>
#include <linux/usb/gadget.h>
#include <linux/usb/hcd.h>
#include <linux/usb/chipidea.h>
#include <linux/regulator/consumer.h>
#include "ci.h"
#include "bits.h"
#include "otg.h"
#include "otg_fsm.h"
/* Add for otg: interact with user space app */
static ssize_t
a_bus_req_show(struct device *dev, struct device_attribute *attr, char *buf)
{
char *next;
unsigned size, t;
struct ci_hdrc *ci = dev_get_drvdata(dev);
next = buf;
size = PAGE_SIZE;
t = scnprintf(next, size, "%d\n", ci->fsm.a_bus_req);
size -= t;
next += t;
return PAGE_SIZE - size;
}
static ssize_t
a_bus_req_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
if (count > 2)
return -1;
mutex_lock(&ci->fsm.lock);
if (buf[0] == '0') {
ci->fsm.a_bus_req = 0;
} else if (buf[0] == '1') {
/* If a_bus_drop is TRUE, a_bus_req can't be set */
if (ci->fsm.a_bus_drop) {
mutex_unlock(&ci->fsm.lock);
return count;
}
ci->fsm.a_bus_req = 1;
if (ci->fsm.otg->state == OTG_STATE_A_PERIPHERAL) {
ci->gadget.host_request_flag = 1;
mutex_unlock(&ci->fsm.lock);
return count;
}
}
ci_otg_queue_work(ci);
mutex_unlock(&ci->fsm.lock);
return count;
}
static DEVICE_ATTR_RW(a_bus_req);
static ssize_t
a_bus_drop_show(struct device *dev, struct device_attribute *attr, char *buf)
{
char *next;
unsigned size, t;
struct ci_hdrc *ci = dev_get_drvdata(dev);
next = buf;
size = PAGE_SIZE;
t = scnprintf(next, size, "%d\n", ci->fsm.a_bus_drop);
size -= t;
next += t;
return PAGE_SIZE - size;
}
static ssize_t
a_bus_drop_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
if (count > 2)
return -1;
mutex_lock(&ci->fsm.lock);
if (buf[0] == '0') {
ci->fsm.a_bus_drop = 0;
} else if (buf[0] == '1') {
ci->fsm.a_bus_drop = 1;
ci->fsm.a_bus_req = 0;
}
ci_otg_queue_work(ci);
mutex_unlock(&ci->fsm.lock);
return count;
}
static DEVICE_ATTR_RW(a_bus_drop);
static ssize_t
b_bus_req_show(struct device *dev, struct device_attribute *attr, char *buf)
{
char *next;
unsigned size, t;
struct ci_hdrc *ci = dev_get_drvdata(dev);
next = buf;
size = PAGE_SIZE;
t = scnprintf(next, size, "%d\n", ci->fsm.b_bus_req);
size -= t;
next += t;
return PAGE_SIZE - size;
}
static ssize_t
b_bus_req_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
if (count > 2)
return -1;
mutex_lock(&ci->fsm.lock);
if (buf[0] == '0')
ci->fsm.b_bus_req = 0;
else if (buf[0] == '1') {
ci->fsm.b_bus_req = 1;
if (ci->fsm.otg->state == OTG_STATE_B_PERIPHERAL) {
ci->gadget.host_request_flag = 1;
mutex_unlock(&ci->fsm.lock);
return count;
}
}
ci_otg_queue_work(ci);
mutex_unlock(&ci->fsm.lock);
return count;
}
static DEVICE_ATTR_RW(b_bus_req);
static ssize_t
a_clr_err_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
if (count > 2)
return -1;
mutex_lock(&ci->fsm.lock);
if (buf[0] == '1')
ci->fsm.a_clr_err = 1;
ci_otg_queue_work(ci);
mutex_unlock(&ci->fsm.lock);
return count;
}
static DEVICE_ATTR_WO(a_clr_err);
static struct attribute *inputs_attrs[] = {
&dev_attr_a_bus_req.attr,
&dev_attr_a_bus_drop.attr,
&dev_attr_b_bus_req.attr,
&dev_attr_a_clr_err.attr,
NULL,
};
static const struct attribute_group inputs_attr_group = {
.name = "inputs",
.attrs = inputs_attrs,
};
/*
* Keep this list in the same order as timers indexed
* by enum otg_fsm_timer in include/linux/usb/otg-fsm.h
*/
static unsigned otg_timer_ms[] = {
TA_WAIT_VRISE,
TA_WAIT_VFALL,
TA_WAIT_BCON,
TA_AIDL_BDIS,
TB_ASE0_BRST,
TA_BIDL_ADIS,
TB_AIDL_BDIS,
TB_SE0_SRP,
TB_SRP_FAIL,
0,
TB_DATA_PLS,
TB_SSEND_SRP,
};
/*
* Add timer to active timer list
*/
static void ci_otg_add_timer(struct ci_hdrc *ci, enum otg_fsm_timer t)
{
unsigned long flags, timer_sec, timer_nsec;
if (t >= NUM_OTG_FSM_TIMERS)
return;
spin_lock_irqsave(&ci->lock, flags);
timer_sec = otg_timer_ms[t] / MSEC_PER_SEC;
timer_nsec = (otg_timer_ms[t] % MSEC_PER_SEC) * NSEC_PER_MSEC;
ci->hr_timeouts[t] = ktime_add(ktime_get(),
ktime_set(timer_sec, timer_nsec));
ci->enabled_otg_timer_bits |= (1 << t);
if ((ci->next_otg_timer == NUM_OTG_FSM_TIMERS) ||
ktime_after(ci->hr_timeouts[ci->next_otg_timer],
ci->hr_timeouts[t])) {
ci->next_otg_timer = t;
hrtimer_start_range_ns(&ci->otg_fsm_hrtimer,
ci->hr_timeouts[t], NSEC_PER_MSEC,
HRTIMER_MODE_ABS);
}
spin_unlock_irqrestore(&ci->lock, flags);
}
/*
* Remove timer from active timer list
*/
static void ci_otg_del_timer(struct ci_hdrc *ci, enum otg_fsm_timer t)
{
unsigned long flags, enabled_timer_bits;
enum otg_fsm_timer cur_timer, next_timer = NUM_OTG_FSM_TIMERS;
if ((t >= NUM_OTG_FSM_TIMERS) ||
!(ci->enabled_otg_timer_bits & (1 << t)))
return;
spin_lock_irqsave(&ci->lock, flags);
ci->enabled_otg_timer_bits &= ~(1 << t);
if (ci->next_otg_timer == t) {
if (ci->enabled_otg_timer_bits == 0) {
spin_unlock_irqrestore(&ci->lock, flags);
/* No enabled timers after delete it */
hrtimer_cancel(&ci->otg_fsm_hrtimer);
spin_lock_irqsave(&ci->lock, flags);
ci->next_otg_timer = NUM_OTG_FSM_TIMERS;
} else {
/* Find the next timer */
enabled_timer_bits = ci->enabled_otg_timer_bits;
for_each_set_bit(cur_timer, &enabled_timer_bits,
NUM_OTG_FSM_TIMERS) {
if ((next_timer == NUM_OTG_FSM_TIMERS) ||
ktime_before(ci->hr_timeouts[next_timer],
ci->hr_timeouts[cur_timer]))
next_timer = cur_timer;
}
}
}
if (next_timer != NUM_OTG_FSM_TIMERS) {
ci->next_otg_timer = next_timer;
hrtimer_start_range_ns(&ci->otg_fsm_hrtimer,
ci->hr_timeouts[next_timer], NSEC_PER_MSEC,
HRTIMER_MODE_ABS);
}
spin_unlock_irqrestore(&ci->lock, flags);
}
/* OTG FSM timer handlers */
static int a_wait_vrise_tmout(struct ci_hdrc *ci)
{
ci->fsm.a_wait_vrise_tmout = 1;
return 0;
}
static int a_wait_vfall_tmout(struct ci_hdrc *ci)
{
ci->fsm.a_wait_vfall_tmout = 1;
return 0;
}
static int a_wait_bcon_tmout(struct ci_hdrc *ci)
{
ci->fsm.a_wait_bcon_tmout = 1;
return 0;
}
static int a_aidl_bdis_tmout(struct ci_hdrc *ci)
{
ci->fsm.a_aidl_bdis_tmout = 1;
return 0;
}
static int b_ase0_brst_tmout(struct ci_hdrc *ci)
{
ci->fsm.b_ase0_brst_tmout = 1;
return 0;
}
static int a_bidl_adis_tmout(struct ci_hdrc *ci)
{
ci->fsm.a_bidl_adis_tmout = 1;
return 0;
}
static int b_aidl_bdis_tmout(struct ci_hdrc *ci)
{
ci->fsm.a_bus_suspend = 1;
return 0;
}
static int b_se0_srp_tmout(struct ci_hdrc *ci)
{
ci->fsm.b_se0_srp = 1;
return 0;
}
static int b_srp_fail_tmout(struct ci_hdrc *ci)
{
ci->fsm.b_srp_done = 1;
return 1;
}
static int b_data_pls_tmout(struct ci_hdrc *ci)
{
ci->fsm.b_srp_done = 1;
ci->fsm.b_bus_req = 0;
if (ci->fsm.power_up)
ci->fsm.power_up = 0;
hw_write_otgsc(ci, OTGSC_HABA, 0);
pm_runtime_put(ci->dev);
return 0;
}
static int b_ssend_srp_tmout(struct ci_hdrc *ci)
{
ci->fsm.b_ssend_srp = 1;
/* only vbus fall below B_sess_vld in b_idle state */
if (ci->fsm.otg->state == OTG_STATE_B_IDLE)
return 0;
else
return 1;
}
/*
* Keep this list in the same order as timers indexed
* by enum otg_fsm_timer in include/linux/usb/otg-fsm.h
*/
static int (*otg_timer_handlers[])(struct ci_hdrc *) = {
a_wait_vrise_tmout, /* A_WAIT_VRISE */
a_wait_vfall_tmout, /* A_WAIT_VFALL */
a_wait_bcon_tmout, /* A_WAIT_BCON */
a_aidl_bdis_tmout, /* A_AIDL_BDIS */
b_ase0_brst_tmout, /* B_ASE0_BRST */
a_bidl_adis_tmout, /* A_BIDL_ADIS */
b_aidl_bdis_tmout, /* B_AIDL_BDIS */
b_se0_srp_tmout, /* B_SE0_SRP */
b_srp_fail_tmout, /* B_SRP_FAIL */
NULL, /* A_WAIT_ENUM */
b_data_pls_tmout, /* B_DATA_PLS */
b_ssend_srp_tmout, /* B_SSEND_SRP */
};
/*
* Enable the next nearest enabled timer if have
*/
static enum hrtimer_restart ci_otg_hrtimer_func(struct hrtimer *t)
{
struct ci_hdrc *ci = container_of(t, struct ci_hdrc, otg_fsm_hrtimer);
ktime_t now, *timeout;
unsigned long enabled_timer_bits;
unsigned long flags;
enum otg_fsm_timer cur_timer, next_timer = NUM_OTG_FSM_TIMERS;
int ret = -EINVAL;
spin_lock_irqsave(&ci->lock, flags);
enabled_timer_bits = ci->enabled_otg_timer_bits;
ci->next_otg_timer = NUM_OTG_FSM_TIMERS;
now = ktime_get();
for_each_set_bit(cur_timer, &enabled_timer_bits, NUM_OTG_FSM_TIMERS) {
if (ktime_compare(now, ci->hr_timeouts[cur_timer]) >= 0) {
ci->enabled_otg_timer_bits &= ~(1 << cur_timer);
if (otg_timer_handlers[cur_timer])
ret = otg_timer_handlers[cur_timer](ci);
} else {
if ((next_timer == NUM_OTG_FSM_TIMERS) ||
ktime_before(ci->hr_timeouts[cur_timer],
ci->hr_timeouts[next_timer]))
next_timer = cur_timer;
}
}
/* Enable the next nearest timer */
if (next_timer < NUM_OTG_FSM_TIMERS) {
timeout = &ci->hr_timeouts[next_timer];
hrtimer_start_range_ns(&ci->otg_fsm_hrtimer, *timeout,
NSEC_PER_MSEC, HRTIMER_MODE_ABS);
ci->next_otg_timer = next_timer;
}
spin_unlock_irqrestore(&ci->lock, flags);
if (!ret)
ci_otg_queue_work(ci);
return HRTIMER_NORESTART;
}
/* Initialize timers */
static int ci_otg_init_timers(struct ci_hdrc *ci)
{
hrtimer_setup(&ci->otg_fsm_hrtimer, ci_otg_hrtimer_func, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
return 0;
}
/* -------------------------------------------------------------*/
/* Operations that will be called from OTG Finite State Machine */
/* -------------------------------------------------------------*/
static void ci_otg_fsm_add_timer(struct otg_fsm *fsm, enum otg_fsm_timer t)
{
struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
if (t < NUM_OTG_FSM_TIMERS)
ci_otg_add_timer(ci, t);
return;
}
static void ci_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t)
{
struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
if (t < NUM_OTG_FSM_TIMERS)
ci_otg_del_timer(ci, t);
return;
}
/*
* A-device drive vbus: turn on vbus regulator and enable port power
* Data pulse irq should be disabled while vbus is on.
*/
static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on)
{
int ret;
struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
if (on) {
/* Enable power */
hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP,
PORTSC_PP);
if (ci->platdata->reg_vbus) {
ret = regulator_enable(ci->platdata->reg_vbus);
if (ret) {
dev_err(ci->dev,
"Failed to enable vbus regulator, ret=%d\n",
ret);
return;
}
}
if (ci->platdata->flags & CI_HDRC_PHY_VBUS_CONTROL)
usb_phy_vbus_on(ci->usb_phy);
/* Disable data pulse irq */
hw_write_otgsc(ci, OTGSC_DPIE, 0);
fsm->a_srp_det = 0;
fsm->power_up = 0;
} else {
if (ci->platdata->reg_vbus)
regulator_disable(ci->platdata->reg_vbus);
if (ci->platdata->flags & CI_HDRC_PHY_VBUS_CONTROL)
usb_phy_vbus_off(ci->usb_phy);
fsm->a_bus_drop = 1;
fsm->a_bus_req = 0;
}
}
/*
* Control data line by Run Stop bit.
*/
static void ci_otg_loc_conn(struct otg_fsm *fsm, int on)
{
struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
if (on)
hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS);
else
hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
}
/*
* Generate SOF by host.
* In host mode, controller will automatically send SOF.
* Suspend will block the data on the port.
*
* This is controlled through usbcore by usb autosuspend,
* so the usb device class driver need support autosuspend,
* otherwise the bus suspend will not happen.
*/
static void ci_otg_loc_sof(struct otg_fsm *fsm, int on)
{
struct usb_device *udev;
if (!fsm->otg->host)
return;
udev = usb_hub_find_child(fsm->otg->host->root_hub, 1);
if (!udev)
return;
if (on) {
usb_disable_autosuspend(udev);
} else {
pm_runtime_set_autosuspend_delay(&udev->dev, 0);
usb_enable_autosuspend(udev);
}
}
/*
* Start SRP pulsing by data-line pulsing,
* no v-bus pulsing followed
*/
static void ci_otg_start_pulse(struct otg_fsm *fsm)
{
struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
/* Hardware Assistant Data pulse */
hw_write_otgsc(ci, OTGSC_HADP, OTGSC_HADP);
pm_runtime_get(ci->dev);
ci_otg_add_timer(ci, B_DATA_PLS);
}
static int ci_otg_start_host(struct otg_fsm *fsm, int on)
{
struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
if (on) {
ci_role_stop(ci);
ci_role_start(ci, CI_ROLE_HOST);
} else {
ci_role_stop(ci);
ci_role_start(ci, CI_ROLE_GADGET);
}
return 0;
}
static int ci_otg_start_gadget(struct otg_fsm *fsm, int on)
{
struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
if (on)
usb_gadget_vbus_connect(&ci->gadget);
else
usb_gadget_vbus_disconnect(&ci->gadget);
return 0;
}
static struct otg_fsm_ops ci_otg_ops = {
.drv_vbus = ci_otg_drv_vbus,
.loc_conn = ci_otg_loc_conn,
.loc_sof = ci_otg_loc_sof,
.start_pulse = ci_otg_start_pulse,
.add_timer = ci_otg_fsm_add_timer,
.del_timer = ci_otg_fsm_del_timer,
.start_host = ci_otg_start_host,
.start_gadget = ci_otg_start_gadget,
};
int ci_otg_fsm_work(struct ci_hdrc *ci)
{
/*
* Don't do fsm transition for B device
* when there is no gadget class driver
*/
if (ci->fsm.id && !(ci->driver) &&
ci->fsm.otg->state < OTG_STATE_A_IDLE)
return 0;
pm_runtime_get_sync(ci->dev);
if (otg_statemachine(&ci->fsm)) {
if (ci->fsm.otg->state == OTG_STATE_A_IDLE) {
/*
* Further state change for cases:
* a_idle to b_idle; or
* a_idle to a_wait_vrise due to ID change(1->0), so
* B-dev becomes A-dev can try to start new session
* consequently; or
* a_idle to a_wait_vrise when power up
*/
if ((ci->fsm.id) || (ci->id_event) ||
(ci->fsm.power_up)) {
ci_otg_queue_work(ci);
} else {
/* Enable data pulse irq */
hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS |
PORTSC_PP, 0);
hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS);
hw_write_otgsc(ci, OTGSC_DPIE, OTGSC_DPIE);
}
if (ci->id_event)
ci->id_event = false;
} else if (ci->fsm.otg->state == OTG_STATE_B_IDLE) {
if (ci->fsm.b_sess_vld) {
ci->fsm.power_up = 0;
/*
* Further transite to b_periphearl state
* when register gadget driver with vbus on
*/
ci_otg_queue_work(ci);
}
} else if (ci->fsm.otg->state == OTG_STATE_A_HOST) {
pm_runtime_put_autosuspend(ci->dev);
return 0;
}
}
pm_runtime_put_sync(ci->dev);
return 0;
}
/*
* Update fsm variables in each state if catching expected interrupts,
* called by otg fsm isr.
*/
static void ci_otg_fsm_event(struct ci_hdrc *ci)
{
u32 intr_sts, otg_bsess_vld, port_conn;
struct otg_fsm *fsm = &ci->fsm;
intr_sts = hw_read_intr_status(ci);
otg_bsess_vld = hw_read_otgsc(ci, OTGSC_BSV);
port_conn = hw_read(ci, OP_PORTSC, PORTSC_CCS);
switch (ci->fsm.otg->state) {
case OTG_STATE_A_WAIT_BCON:
if (port_conn) {
fsm->b_conn = 1;
fsm->a_bus_req = 1;
ci_otg_queue_work(ci);
}
break;
case OTG_STATE_B_IDLE:
if (otg_bsess_vld && (intr_sts & USBi_PCI) && port_conn) {
fsm->b_sess_vld = 1;
ci_otg_queue_work(ci);
}
break;
case OTG_STATE_B_PERIPHERAL:
if ((intr_sts & USBi_SLI) && port_conn && otg_bsess_vld) {
ci_otg_add_timer(ci, B_AIDL_BDIS);
} else if (intr_sts & USBi_PCI) {
ci_otg_del_timer(ci, B_AIDL_BDIS);
if (fsm->a_bus_suspend == 1)
fsm->a_bus_suspend = 0;
}
break;
case OTG_STATE_B_HOST:
if ((intr_sts & USBi_PCI) && !port_conn) {
fsm->a_conn = 0;
fsm->b_bus_req = 0;
ci_otg_queue_work(ci);
}
break;
case OTG_STATE_A_PERIPHERAL:
if (intr_sts & USBi_SLI) {
fsm->b_bus_suspend = 1;
/*
* Init a timer to know how long this suspend
* will continue, if time out, indicates B no longer
* wants to be host role
*/
ci_otg_add_timer(ci, A_BIDL_ADIS);
}
if (intr_sts & USBi_URI)
ci_otg_del_timer(ci, A_BIDL_ADIS);
if (intr_sts & USBi_PCI) {
if (fsm->b_bus_suspend == 1) {
ci_otg_del_timer(ci, A_BIDL_ADIS);
fsm->b_bus_suspend = 0;
}
}
break;
case OTG_STATE_A_SUSPEND:
if ((intr_sts & USBi_PCI) && !port_conn) {
fsm->b_conn = 0;
/* if gadget driver is binded */
if (ci->driver) {
/* A device to be peripheral mode */
ci->gadget.is_a_peripheral = 1;
}
ci_otg_queue_work(ci);
}
break;
case OTG_STATE_A_HOST:
if ((intr_sts & USBi_PCI) && !port_conn) {
fsm->b_conn = 0;
ci_otg_queue_work(ci);
}
break;
case OTG_STATE_B_WAIT_ACON:
if ((intr_sts & USBi_PCI) && port_conn) {
fsm->a_conn = 1;
ci_otg_queue_work(ci);
}
break;
default:
break;
}
}
/*
* ci_otg_irq - otg fsm related irq handling
* and also update otg fsm variable by monitoring usb host and udc
* state change interrupts.
* @ci: ci_hdrc
*/
irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci)
{
irqreturn_t retval = IRQ_NONE;
u32 otgsc, otg_int_src = 0;
struct otg_fsm *fsm = &ci->fsm;
otgsc = hw_read_otgsc(ci, ~0);
otg_int_src = otgsc & OTGSC_INT_STATUS_BITS & (otgsc >> 8);
fsm->id = (otgsc & OTGSC_ID) ? 1 : 0;
if (otg_int_src) {
if (otg_int_src & OTGSC_DPIS) {
hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS);
fsm->a_srp_det = 1;
fsm->a_bus_drop = 0;
} else if (otg_int_src & OTGSC_IDIS) {
hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS);
if (fsm->id == 0) {
fsm->a_bus_drop = 0;
fsm->a_bus_req = 1;
ci->id_event = true;
}
} else if (otg_int_src & OTGSC_BSVIS) {
hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS);
if (otgsc & OTGSC_BSV) {
fsm->b_sess_vld = 1;
ci_otg_del_timer(ci, B_SSEND_SRP);
ci_otg_del_timer(ci, B_SRP_FAIL);
fsm->b_ssend_srp = 0;
} else {
fsm->b_sess_vld = 0;
if (fsm->id)
ci_otg_add_timer(ci, B_SSEND_SRP);
}
} else if (otg_int_src & OTGSC_AVVIS) {
hw_write_otgsc(ci, OTGSC_AVVIS, OTGSC_AVVIS);
if (otgsc & OTGSC_AVV) {
fsm->a_vbus_vld = 1;
} else {
fsm->a_vbus_vld = 0;
fsm->b_conn = 0;
}
}
ci_otg_queue_work(ci);
return IRQ_HANDLED;
}
ci_otg_fsm_event(ci);
return retval;
}
void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci)
{
ci_otg_queue_work(ci);
}
int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
{
int retval = 0;
if (ci->phy)
ci->otg.phy = ci->phy;
else
ci->otg.usb_phy = ci->usb_phy;
ci->otg.gadget = &ci->gadget;
ci->fsm.otg = &ci->otg;
ci->fsm.power_up = 1;
ci->fsm.id = hw_read_otgsc(ci, OTGSC_ID) ? 1 : 0;
ci->fsm.otg->state = OTG_STATE_UNDEFINED;
ci->fsm.ops = &ci_otg_ops;
ci->gadget.hnp_polling_support = 1;
ci->fsm.host_req_flag = devm_kzalloc(ci->dev, 1, GFP_KERNEL);
if (!ci->fsm.host_req_flag)
return -ENOMEM;
mutex_init(&ci->fsm.lock);
retval = ci_otg_init_timers(ci);
if (retval) {
dev_err(ci->dev, "Couldn't init OTG timers\n");
return retval;
}
ci->enabled_otg_timer_bits = 0;
ci->next_otg_timer = NUM_OTG_FSM_TIMERS;
retval = sysfs_create_group(&ci->dev->kobj, &inputs_attr_group);
if (retval < 0) {
dev_dbg(ci->dev,
"Can't register sysfs attr group: %d\n", retval);
return retval;
}
/* Enable A vbus valid irq */
hw_write_otgsc(ci, OTGSC_AVVIE, OTGSC_AVVIE);
if (ci->fsm.id) {
ci->fsm.b_ssend_srp =
hw_read_otgsc(ci, OTGSC_BSV) ? 0 : 1;
ci->fsm.b_sess_vld =
hw_read_otgsc(ci, OTGSC_BSV) ? 1 : 0;
/* Enable BSV irq */
hw_write_otgsc(ci, OTGSC_BSVIE, OTGSC_BSVIE);
}
return 0;
}
void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci)
{
sysfs_remove_group(&ci->dev->kobj, &inputs_attr_group);
}
+101
View File
@@ -0,0 +1,101 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2014 Freescale Semiconductor, Inc.
*
* Author: Jun Li
*/
#ifndef __DRIVERS_USB_CHIPIDEA_OTG_FSM_H
#define __DRIVERS_USB_CHIPIDEA_OTG_FSM_H
#include <linux/usb/otg-fsm.h>
/*
* A-DEVICE timing constants
*/
/* Wait for VBUS Rise */
#define TA_WAIT_VRISE (100) /* a_wait_vrise: section 7.1.2
* a_wait_vrise_tmr: section 7.4.5.1
* TA_VBUS_RISE <= 100ms, section 4.4
* Table 4-1: Electrical Characteristics
* ->DC Electrical Timing
*/
/* Wait for VBUS Fall */
#define TA_WAIT_VFALL (1000) /* a_wait_vfall: section 7.1.7
* a_wait_vfall_tmr: section: 7.4.5.2
*/
/* Wait for B-Connect */
#define TA_WAIT_BCON (10000) /* a_wait_bcon: section 7.1.3
* TA_WAIT_BCON: should be between 1100
* and 30000 ms, section 5.5, Table 5-1
*/
/* A-Idle to B-Disconnect */
#define TA_AIDL_BDIS (5000) /* a_suspend min 200 ms, section 5.2.1
* TA_AIDL_BDIS: section 5.5, Table 5-1
*/
/* B-Idle to A-Disconnect */
#define TA_BIDL_ADIS (500) /* TA_BIDL_ADIS: section 5.2.1
* 500ms is used for B switch to host
* for safe
*/
/*
* B-device timing constants
*/
/* Data-Line Pulse Time*/
#define TB_DATA_PLS (10) /* b_srp_init,continue 5~10ms
* section:5.1.3
*/
/* SRP Fail Time */
#define TB_SRP_FAIL (6000) /* b_srp_init,fail time 5~6s
* section:5.1.6
*/
/* A-SE0 to B-Reset */
#define TB_ASE0_BRST (155) /* minimum 155 ms, section:5.3.1 */
/* SE0 Time Before SRP */
#define TB_SE0_SRP (1000) /* b_idle,minimum 1s, section:5.1.2 */
/* SSEND time before SRP */
#define TB_SSEND_SRP (1500) /* minimum 1.5 sec, section:5.1.2 */
#define TB_AIDL_BDIS (20) /* 4ms ~ 150ms, section 5.2.1 */
#if IS_ENABLED(CONFIG_USB_OTG_FSM)
int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci);
int ci_otg_fsm_work(struct ci_hdrc *ci);
irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci);
void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci);
void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci);
#else
static inline int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
{
return 0;
}
static inline int ci_otg_fsm_work(struct ci_hdrc *ci)
{
return -ENXIO;
}
static inline irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci)
{
return IRQ_NONE;
}
static inline void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci)
{
}
static inline void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci)
{
}
#endif
#endif /* __DRIVERS_USB_CHIPIDEA_OTG_FSM_H */
+23
View File
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Chipidea Device Mode Trace Support
*
* Copyright (C) 2020 NXP
*
* Author: Peter Chen <peter.chen@nxp.com>
*/
#define CREATE_TRACE_POINTS
#include "trace.h"
void ci_log(struct ci_hdrc *ci, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
trace_ci_log(ci, &vaf);
va_end(args);
}
+92
View File
@@ -0,0 +1,92 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Trace support header file for device mode
*
* Copyright (C) 2020 NXP
*
* Author: Peter Chen <peter.chen@nxp.com>
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM chipidea
#if !defined(__LINUX_CHIPIDEA_TRACE) || defined(TRACE_HEADER_MULTI_READ)
#define __LINUX_CHIPIDEA_TRACE
#include <linux/types.h>
#include <linux/tracepoint.h>
#include <linux/usb/chipidea.h>
#include "ci.h"
#include "udc.h"
#define CHIPIDEA_MSG_MAX 500
void ci_log(struct ci_hdrc *ci, const char *fmt, ...);
TRACE_EVENT(ci_log,
TP_PROTO(struct ci_hdrc *ci, struct va_format *vaf),
TP_ARGS(ci, vaf),
TP_STRUCT__entry(
__string(name, dev_name(ci->dev))
__vstring(msg, vaf->fmt, vaf->va)
),
TP_fast_assign(
__assign_str(name);
__assign_vstr(msg, vaf->fmt, vaf->va);
),
TP_printk("%s: %s", __get_str(name), __get_str(msg))
);
DECLARE_EVENT_CLASS(ci_log_trb,
TP_PROTO(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, struct td_node *td),
TP_ARGS(hwep, hwreq, td),
TP_STRUCT__entry(
__string(name, hwep->name)
__field(struct td_node *, td)
__field(struct usb_request *, req)
__field(dma_addr_t, dma)
__field(s32, td_remaining_size)
__field(u32, next)
__field(u32, token)
__field(u32, type)
),
TP_fast_assign(
__assign_str(name);
__entry->req = &hwreq->req;
__entry->td = td;
__entry->dma = td->dma;
__entry->td_remaining_size = td->td_remaining_size;
__entry->next = le32_to_cpu(td->ptr->next);
__entry->token = le32_to_cpu(td->ptr->token);
__entry->type = usb_endpoint_type(hwep->ep.desc);
),
TP_printk("%s: req: %p, td: %p, td_dma_address: %pad, remaining_size: %d, "
"next: %x, total bytes: %d, status: %lx",
__get_str(name), __entry->req, __entry->td, &__entry->dma,
__entry->td_remaining_size, __entry->next,
(int)((__entry->token & TD_TOTAL_BYTES) >> __ffs(TD_TOTAL_BYTES)),
__entry->token & TD_STATUS
)
);
DEFINE_EVENT(ci_log_trb, ci_prepare_td,
TP_PROTO(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, struct td_node *td),
TP_ARGS(hwep, hwreq, td)
);
DEFINE_EVENT(ci_log_trb, ci_complete_td,
TP_PROTO(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, struct td_node *td),
TP_ARGS(hwep, hwreq, td)
);
#endif /* __LINUX_CHIPIDEA_TRACE */
/* this part must be outside header guard */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace
#include <trace/define_trace.h>
File diff suppressed because it is too large Load Diff
+100
View File
@@ -0,0 +1,100 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* udc.h - ChipIdea UDC structures
*
* Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved.
*
* Author: David Lopo
*/
#ifndef __DRIVERS_USB_CHIPIDEA_UDC_H
#define __DRIVERS_USB_CHIPIDEA_UDC_H
#include <linux/list.h>
#define CTRL_PAYLOAD_MAX 64
#define RX 0 /* similar to USB_DIR_OUT but can be used as an index */
#define TX 1 /* similar to USB_DIR_IN but can be used as an index */
/* DMA layout of transfer descriptors */
struct ci_hw_td {
/* 0 */
__le32 next;
#define TD_TERMINATE BIT(0)
#define TD_ADDR_MASK (0xFFFFFFEUL << 5)
/* 1 */
__le32 token;
#define TD_STATUS (0x00FFUL << 0)
#define TD_STATUS_TR_ERR BIT(3)
#define TD_STATUS_DT_ERR BIT(5)
#define TD_STATUS_HALTED BIT(6)
#define TD_STATUS_ACTIVE BIT(7)
#define TD_MULTO (0x0003UL << 10)
#define TD_IOC BIT(15)
#define TD_TOTAL_BYTES (0x7FFFUL << 16)
/* 2 */
__le32 page[5];
#define TD_CURR_OFFSET (0x0FFFUL << 0)
#define TD_FRAME_NUM (0x07FFUL << 0)
#define TD_RESERVED_MASK (0x0FFFUL << 0)
} __attribute__ ((packed, aligned(4)));
/* DMA layout of queue heads */
struct ci_hw_qh {
/* 0 */
__le32 cap;
#define QH_IOS BIT(15)
#define QH_MAX_PKT (0x07FFUL << 16)
#define QH_ZLT BIT(29)
#define QH_MULT (0x0003UL << 30)
#define QH_ISO_MULT(x) ((x >> 11) & 0x03)
/* 1 */
__le32 curr;
/* 2 - 8 */
struct ci_hw_td td;
/* 9 */
__le32 RESERVED;
struct usb_ctrlrequest setup;
} __attribute__ ((packed, aligned(4)));
struct td_node {
struct list_head td;
dma_addr_t dma;
struct ci_hw_td *ptr;
int td_remaining_size;
};
/**
* struct ci_hw_req - usb request representation
* @req: request structure for gadget drivers
* @queue: link to QH list
* @tds: link to TD list
* @sgt: hold original sglist when bounce sglist
*/
struct ci_hw_req {
struct usb_request req;
struct list_head queue;
struct list_head tds;
struct sg_table sgt;
};
#ifdef CONFIG_USB_CHIPIDEA_UDC
int ci_hdrc_gadget_init(struct ci_hdrc *ci);
void ci_hdrc_gadget_destroy(struct ci_hdrc *ci);
#else
static inline int ci_hdrc_gadget_init(struct ci_hdrc *ci)
{
return -ENXIO;
}
static inline void ci_hdrc_gadget_destroy(struct ci_hdrc *ci)
{
}
#endif
#endif /* __DRIVERS_USB_CHIPIDEA_UDC_H */
+108
View File
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2016 Linaro Ltd.
*/
#include <linux/device.h>
#include <linux/usb/chipidea.h>
#include <linux/ulpi/interface.h>
#include "ci.h"
#define ULPI_WAKEUP BIT(31)
#define ULPI_RUN BIT(30)
#define ULPI_WRITE BIT(29)
#define ULPI_SYNC_STATE BIT(27)
#define ULPI_ADDR(n) ((n) << 16)
#define ULPI_DATA(n) (n)
static int ci_ulpi_wait(struct ci_hdrc *ci, u32 mask)
{
unsigned long usec = 10000;
while (usec--) {
if (!hw_read(ci, OP_ULPI_VIEWPORT, mask))
return 0;
udelay(1);
}
return -ETIMEDOUT;
}
static int ci_ulpi_read(struct device *dev, u8 addr)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
int ret;
hw_write(ci, OP_ULPI_VIEWPORT, 0xffffffff, ULPI_WRITE | ULPI_WAKEUP);
ret = ci_ulpi_wait(ci, ULPI_WAKEUP);
if (ret)
return ret;
hw_write(ci, OP_ULPI_VIEWPORT, 0xffffffff, ULPI_RUN | ULPI_ADDR(addr));
ret = ci_ulpi_wait(ci, ULPI_RUN);
if (ret)
return ret;
return hw_read(ci, OP_ULPI_VIEWPORT, GENMASK(15, 8)) >> 8;
}
static int ci_ulpi_write(struct device *dev, u8 addr, u8 val)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
int ret;
hw_write(ci, OP_ULPI_VIEWPORT, 0xffffffff, ULPI_WRITE | ULPI_WAKEUP);
ret = ci_ulpi_wait(ci, ULPI_WAKEUP);
if (ret)
return ret;
hw_write(ci, OP_ULPI_VIEWPORT, 0xffffffff,
ULPI_RUN | ULPI_WRITE | ULPI_ADDR(addr) | val);
return ci_ulpi_wait(ci, ULPI_RUN);
}
int ci_ulpi_init(struct ci_hdrc *ci)
{
if (ci->platdata->phy_mode != USBPHY_INTERFACE_MODE_ULPI)
return 0;
/*
* Set PORTSC correctly so we can read/write ULPI registers for
* identification purposes
*/
hw_phymode_configure(ci);
ci->ulpi_ops.read = ci_ulpi_read;
ci->ulpi_ops.write = ci_ulpi_write;
ci->ulpi = ulpi_register_interface(ci->dev, &ci->ulpi_ops);
if (IS_ERR(ci->ulpi))
dev_err(ci->dev, "failed to register ULPI interface");
return PTR_ERR_OR_ZERO(ci->ulpi);
}
void ci_ulpi_exit(struct ci_hdrc *ci)
{
if (ci->ulpi) {
ulpi_unregister_interface(ci->ulpi);
ci->ulpi = NULL;
}
}
int ci_ulpi_resume(struct ci_hdrc *ci)
{
int cnt = 100000;
if (ci->platdata->phy_mode != USBPHY_INTERFACE_MODE_ULPI)
return 0;
while (cnt-- > 0) {
if (hw_read(ci, OP_ULPI_VIEWPORT, ULPI_SYNC_STATE))
return 0;
udelay(1);
}
return -ETIMEDOUT;
}
File diff suppressed because it is too large Load Diff