diff options
Diffstat (limited to 'drivers/usb/chipidea')
25 files changed, 3663 insertions, 2299 deletions
diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 608a2aeb400..77b47d82c9a 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -1,6 +1,6 @@ config USB_CHIPIDEA tristate "ChipIdea Highspeed Dual Role Controller" - depends on USB || USB_GADGET + depends on ((USB_EHCI_HCD && USB_GADGET) || (USB_EHCI_HCD && !USB_GADGET) || (!USB_EHCI_HCD && USB_GADGET)) && HAS_DMA help Say Y here if your system has a dual role high speed USB controller based on ChipIdea silicon IP. Currently, only the @@ -12,14 +12,13 @@ if USB_CHIPIDEA config USB_CHIPIDEA_UDC bool "ChipIdea device controller" - depends on USB_GADGET=y || USB_GADGET=USB_CHIPIDEA + 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=y || USB=USB_CHIPIDEA depends on USB_EHCI_HCD select USB_EHCI_ROOT_HUB_TT help diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile index d92ca325b10..2f099c7df7b 100644 --- a/drivers/usb/chipidea/Makefile +++ b/drivers/usb/chipidea/Makefile @@ -2,20 +2,22 @@ ccflags-$(CONFIG_USB_CHIPIDEA_DEBUG) := -DDEBUG obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc.o -ci_hdrc-y := core.o +ci_hdrc-y := core.o otg.o ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC) += udc.o ci_hdrc-$(CONFIG_USB_CHIPIDEA_HOST) += host.o ci_hdrc-$(CONFIG_USB_CHIPIDEA_DEBUG) += debug.o +ci_hdrc-$(CONFIG_USB_OTG_FSM) += otg_fsm.o # Glue/Bridge layers go here -obj-$(CONFIG_USB_CHIPIDEA) += ci13xxx_msm.o +obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc_msm.o +obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc_zevio.o # PCI doesn't provide stubs, need to check ifneq ($(CONFIG_PCI),) - obj-$(CONFIG_USB_CHIPIDEA) += ci13xxx_pci.o + obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc_pci.o endif -ifneq ($(CONFIG_OF_DEVICE),) - obj-$(CONFIG_USB_CHIPIDEA) += ci13xxx_imx.o usbmisc_imx6q.o +ifneq ($(CONFIG_OF),) + obj-$(CONFIG_USB_CHIPIDEA) += usbmisc_imx.o ci_hdrc_imx.o endif diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h index 050de8562a0..ca57e3dcd3d 100644 --- a/drivers/usb/chipidea/bits.h +++ b/drivers/usb/chipidea/bits.h @@ -44,17 +44,44 @@ #define DEVICEADDR_USBADR (0x7FUL << 25) /* 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_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_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) @@ -65,11 +92,21 @@ #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) diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index e25d1263da1..9563cb56d56 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -17,18 +17,49 @@ #include <linux/irqreturn.h> #include <linux/usb.h> #include <linux/usb/gadget.h> +#include <linux/usb/otg-fsm.h> /****************************************************************************** * DEFINE *****************************************************************************/ -#define CI13XXX_PAGE_SIZE 4096ul /* page size for TD's */ +#define TD_PAGE_COUNT 5 +#define CI_HDRC_PAGE_SIZE 4096ul /* page size for TD's */ #define ENDPT_MAX 32 /****************************************************************************** + * REGISTERS + *****************************************************************************/ +/* 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_DEVICEADDR, + OP_ENDPTLISTADDR, + 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 ci13xxx_ep - endpoint representation + * struct ci_hw_ep - endpoint representation * @ep: endpoint structure for gadget drivers * @dir: endpoint direction (TX/RX) * @num: endpoint number @@ -40,7 +71,7 @@ * @lock: pointer to controller's spinlock * @td_pool: pointer to controller's TD pool */ -struct ci13xxx_ep { +struct ci_hw_ep { struct usb_ep ep; u8 dir; u8 num; @@ -48,15 +79,16 @@ struct ci13xxx_ep { char name[16]; struct { struct list_head queue; - struct ci13xxx_qh *ptr; + struct ci_hw_qh *ptr; dma_addr_t dma; } qh; int wedge; /* global resources */ - struct ci13xxx *ci; + struct ci_hdrc *ci; spinlock_t *lock; struct dma_pool *td_pool; + struct td_node *pending_td; }; enum ci_role { @@ -73,9 +105,9 @@ enum ci_role { * name: role name string (host/gadget) */ struct ci_role_driver { - int (*start)(struct ci13xxx *); - void (*stop)(struct ci13xxx *); - irqreturn_t (*irq)(struct ci13xxx *); + int (*start)(struct ci_hdrc *); + void (*stop)(struct ci_hdrc *); + irqreturn_t (*irq)(struct ci_hdrc *); const char *name; }; @@ -96,11 +128,11 @@ struct hw_bank { void __iomem *cap; void __iomem *op; size_t size; - void __iomem **regmap; + void __iomem *regmap[OP_LAST + 1]; }; /** - * struct ci13xxx - chipidea device representation + * struct ci_hdrc - chipidea device representation * @dev: pointer to parent device * @lock: access synchronization * @hw_bank: hardware register mapping @@ -108,6 +140,8 @@ struct hw_bank { * @roles: array of supported roles for this controller * @role: current role * @is_otg: if the device is otg-capable + * @fsm: otg finite state machine + * @fsm_timer: pointer to timer list of otg fsm * @work: work for role changing * @wq: workqueue thread * @qh_pool: allocation pool for queue heads @@ -115,7 +149,7 @@ struct hw_bank { * @gadget: device side representation for peripheral controller * @driver: gadget driver * @hw_ep_max: total number of endpoints supported by hardware - * @ci13xxx_ep: array of endpoints + * @ci_hw_ep: array of endpoints * @ep0_dir: ep0 direction * @ep0out: pointer to ep0 OUT endpoint * @ep0in: pointer to ep0 IN endpoint @@ -129,8 +163,13 @@ struct hw_bank { * @vbus_active: is VBUS active * @transceiver: pointer to USB PHY, if any * @hcd: pointer to usb_hcd for ehci host driver + * @debugfs: root dentry for this controller in debugfs + * @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 */ -struct ci13xxx { +struct ci_hdrc { struct device *dev; spinlock_t lock; struct hw_bank hw_bank; @@ -138,8 +177,9 @@ struct ci13xxx { struct ci_role_driver *roles[CI_ROLE_END]; enum ci_role role; bool is_otg; + struct otg_fsm fsm; + struct ci_otg_fsm_timer_list *fsm_timer; struct work_struct work; - struct work_struct vbus_work; struct workqueue_struct *wq; struct dma_pool *qh_pool; @@ -148,9 +188,9 @@ struct ci13xxx { struct usb_gadget gadget; struct usb_gadget_driver *driver; unsigned hw_ep_max; - struct ci13xxx_ep ci13xxx_ep[ENDPT_MAX]; + struct ci_hw_ep ci_hw_ep[ENDPT_MAX]; u32 ep0_dir; - struct ci13xxx_ep *ep0out, *ep0in; + struct ci_hw_ep *ep0out, *ep0in; struct usb_request *status; bool setaddr; @@ -159,21 +199,23 @@ struct ci13xxx { u8 suspended; u8 test_mode; - struct ci13xxx_platform_data *platdata; + struct ci_hdrc_platform_data *platdata; int vbus_active; - /* FIXME: some day, we'll not use global phy */ - bool global_phy; struct usb_phy *transceiver; struct usb_hcd *hcd; + struct dentry *debugfs; + bool id_event; + bool b_sess_valid_event; + bool imx28_write_fix; }; -static inline struct ci_role_driver *ci_role(struct ci13xxx *ci) +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 ci13xxx *ci, enum ci_role role) +static inline int ci_role_start(struct ci_hdrc *ci, enum ci_role role) { int ret; @@ -189,7 +231,7 @@ static inline int ci_role_start(struct ci13xxx *ci, enum ci_role role) return ret; } -static inline void ci_role_stop(struct ci13xxx *ci) +static inline void ci_role_stop(struct ci_hdrc *ci) { enum ci_role role = ci->role; @@ -201,51 +243,6 @@ static inline void ci_role_stop(struct ci13xxx *ci) ci->roles[role]->stop(ci); } -/****************************************************************************** - * REGISTERS - *****************************************************************************/ -/* register size */ -#define REG_BITS (32) - -/* register indices */ -enum ci13xxx_regs { - CAP_CAPLENGTH, - CAP_HCCPARAMS, - CAP_DCCPARAMS, - CAP_TESTMODE, - CAP_LAST = CAP_TESTMODE, - OP_USBCMD, - OP_USBSTS, - OP_USBINTR, - OP_DEVICEADDR, - OP_ENDPTLISTADDR, - 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, -}; - -/** - * ffs_nr: find first (least significant) bit set - * @x: the word to search - * - * This function returns bit number (instead of position) - */ -static inline int ffs_nr(u32 x) -{ - int n = ffs(x); - - return n ? n-1 : 32; -} - /** * hw_read: reads from a hw register * @reg: register index @@ -253,25 +250,45 @@ static inline int ffs_nr(u32 x) * * This function returns register contents */ -static inline u32 hw_read(struct ci13xxx *ci, enum ci13xxx_regs reg, u32 mask) +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 * @reg: register index * @mask: bitfield mask * @data: new value */ -static inline void hw_write(struct ci13xxx *ci, enum ci13xxx_regs reg, +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); - iowrite32(data, ci->hw_bank.regmap[reg]); + __hw_write(ci, data, ci->hw_bank.regmap[reg]); } /** @@ -281,12 +298,12 @@ static inline void hw_write(struct ci13xxx *ci, enum ci13xxx_regs reg, * * This function returns register contents */ -static inline u32 hw_test_and_clear(struct ci13xxx *ci, enum ci13xxx_regs reg, +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; - iowrite32(val, ci->hw_bank.regmap[reg]); + __hw_write(ci, val, ci->hw_bank.regmap[reg]); return val; } @@ -298,19 +315,40 @@ static inline u32 hw_test_and_clear(struct ci13xxx *ci, enum ci13xxx_regs reg, * * This function returns register contents */ -static inline u32 hw_test_and_write(struct ci13xxx *ci, enum ci13xxx_regs reg, +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_nr(mask); + return (val & mask) >> __ffs(mask); +} + +/** + * ci_otg_is_fsm_mode: runtime check if otg controller + * is in otg fsm mode. + */ +static inline bool ci_otg_is_fsm_mode(struct ci_hdrc *ci) +{ +#ifdef CONFIG_USB_OTG_FSM + return ci->is_otg && ci->roles[CI_ROLE_HOST] && + ci->roles[CI_ROLE_GADGET]; +#else + return false; +#endif } -int hw_device_reset(struct ci13xxx *ci, u32 mode); +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, u32 mode); + +int hw_port_test_set(struct ci_hdrc *ci, u8 mode); -int hw_port_test_set(struct ci13xxx *ci, u8 mode); +u8 hw_port_test_get(struct ci_hdrc *ci); -u8 hw_port_test_get(struct ci13xxx *ci); +int hw_wait_reg(struct ci_hdrc *ci, enum ci_hw_regs reg, u32 mask, + u32 value, unsigned int timeout_ms); #endif /* __DRIVERS_USB_CHIPIDEA_CI_H */ diff --git a/drivers/usb/chipidea/ci13xxx_imx.c b/drivers/usb/chipidea/ci13xxx_imx.c deleted file mode 100644 index 8c291220be7..00000000000 --- a/drivers/usb/chipidea/ci13xxx_imx.c +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright 2012 Freescale Semiconductor, Inc. - * Copyright (C) 2012 Marek Vasut <marex@denx.de> - * on behalf of DENX Software Engineering GmbH - * - * The code contained herein is licensed under the GNU General Public - * License. You may obtain a copy of the GNU General Public License - * Version 2 or later at the following locations: - * - * http://www.opensource.org/licenses/gpl-license.html - * http://www.gnu.org/copyleft/gpl.html - */ - -#include <linux/module.h> -#include <linux/of_platform.h> -#include <linux/of_gpio.h> -#include <linux/platform_device.h> -#include <linux/pm_runtime.h> -#include <linux/dma-mapping.h> -#include <linux/usb/chipidea.h> -#include <linux/clk.h> -#include <linux/regulator/consumer.h> -#include <linux/pinctrl/consumer.h> - -#include "ci.h" -#include "ci13xxx_imx.h" - -#define pdev_to_phy(pdev) \ - ((struct usb_phy *)platform_get_drvdata(pdev)) - -struct ci13xxx_imx_data { - struct device_node *phy_np; - struct usb_phy *phy; - struct platform_device *ci_pdev; - struct clk *clk; - struct regulator *reg_vbus; -}; - -static const struct usbmisc_ops *usbmisc_ops; - -/* Common functions shared by usbmisc drivers */ - -int usbmisc_set_ops(const struct usbmisc_ops *ops) -{ - if (usbmisc_ops) - return -EBUSY; - - usbmisc_ops = ops; - - return 0; -} -EXPORT_SYMBOL_GPL(usbmisc_set_ops); - -void usbmisc_unset_ops(const struct usbmisc_ops *ops) -{ - usbmisc_ops = NULL; -} -EXPORT_SYMBOL_GPL(usbmisc_unset_ops); - -int usbmisc_get_init_data(struct device *dev, struct usbmisc_usb_device *usbdev) -{ - struct device_node *np = dev->of_node; - struct of_phandle_args args; - int ret; - - usbdev->dev = dev; - - 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); - memset(usbdev, 0, sizeof(*usbdev)); - return ret; - } - usbdev->index = args.args[0]; - of_node_put(args.np); - - if (of_find_property(np, "disable-over-current", NULL)) - usbdev->disable_oc = 1; - - return 0; -} -EXPORT_SYMBOL_GPL(usbmisc_get_init_data); - -/* End of common functions shared by usbmisc drivers*/ - -static struct ci13xxx_platform_data ci13xxx_imx_platdata = { - .name = "ci13xxx_imx", - .flags = CI13XXX_REQUIRE_TRANSCEIVER | - CI13XXX_PULLUP_ON_VBUS | - CI13XXX_DISABLE_STREAMING, - .capoffset = DEF_CAPOFFSET, -}; - -static int ci13xxx_imx_probe(struct platform_device *pdev) -{ - struct ci13xxx_imx_data *data; - struct platform_device *plat_ci, *phy_pdev; - struct device_node *phy_np; - struct resource *res; - struct regulator *reg_vbus; - struct pinctrl *pinctrl; - int ret; - - if (of_find_property(pdev->dev.of_node, "fsl,usbmisc", NULL) - && !usbmisc_ops) - return -EPROBE_DEFER; - - data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); - if (!data) { - dev_err(&pdev->dev, "Failed to allocate CI13xxx-IMX data!\n"); - return -ENOMEM; - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&pdev->dev, "Can't get device resources!\n"); - return -ENOENT; - } - - pinctrl = devm_pinctrl_get_select_default(&pdev->dev); - if (IS_ERR(pinctrl)) - dev_warn(&pdev->dev, "pinctrl get/select failed, err=%ld\n", - PTR_ERR(pinctrl)); - - data->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(data->clk)) { - dev_err(&pdev->dev, - "Failed to get clock, err=%ld\n", PTR_ERR(data->clk)); - return PTR_ERR(data->clk); - } - - ret = clk_prepare_enable(data->clk); - if (ret) { - dev_err(&pdev->dev, - "Failed to prepare or enable clock, err=%d\n", ret); - return ret; - } - - phy_np = of_parse_phandle(pdev->dev.of_node, "fsl,usbphy", 0); - if (phy_np) { - data->phy_np = phy_np; - phy_pdev = of_find_device_by_node(phy_np); - if (phy_pdev) { - struct usb_phy *phy; - phy = pdev_to_phy(phy_pdev); - if (phy && - try_module_get(phy_pdev->dev.driver->owner)) { - usb_phy_init(phy); - data->phy = phy; - } - } - } - - /* we only support host now, so enable vbus here */ - reg_vbus = devm_regulator_get(&pdev->dev, "vbus"); - if (!IS_ERR(reg_vbus)) { - ret = regulator_enable(reg_vbus); - if (ret) { - dev_err(&pdev->dev, - "Failed to enable vbus regulator, err=%d\n", - ret); - goto put_np; - } - data->reg_vbus = reg_vbus; - } else { - reg_vbus = NULL; - } - - ci13xxx_imx_platdata.phy = data->phy; - - if (!pdev->dev.dma_mask) { - pdev->dev.dma_mask = devm_kzalloc(&pdev->dev, - sizeof(*pdev->dev.dma_mask), GFP_KERNEL); - if (!pdev->dev.dma_mask) { - ret = -ENOMEM; - dev_err(&pdev->dev, "Failed to alloc dma_mask!\n"); - goto err; - } - *pdev->dev.dma_mask = DMA_BIT_MASK(32); - dma_set_coherent_mask(&pdev->dev, *pdev->dev.dma_mask); - } - - if (usbmisc_ops && usbmisc_ops->init) { - ret = usbmisc_ops->init(&pdev->dev); - if (ret) { - dev_err(&pdev->dev, - "usbmisc init failed, ret=%d\n", ret); - goto err; - } - } - - plat_ci = ci13xxx_add_device(&pdev->dev, - pdev->resource, pdev->num_resources, - &ci13xxx_imx_platdata); - if (IS_ERR(plat_ci)) { - ret = PTR_ERR(plat_ci); - dev_err(&pdev->dev, - "Can't register ci_hdrc platform device, err=%d\n", - ret); - goto err; - } - - data->ci_pdev = plat_ci; - platform_set_drvdata(pdev, data); - - pm_runtime_no_callbacks(&pdev->dev); - pm_runtime_enable(&pdev->dev); - - return 0; - -err: - if (reg_vbus) - regulator_disable(reg_vbus); -put_np: - if (phy_np) - of_node_put(phy_np); - clk_disable_unprepare(data->clk); - return ret; -} - -static int ci13xxx_imx_remove(struct platform_device *pdev) -{ - struct ci13xxx_imx_data *data = platform_get_drvdata(pdev); - - pm_runtime_disable(&pdev->dev); - ci13xxx_remove_device(data->ci_pdev); - - if (data->reg_vbus) - regulator_disable(data->reg_vbus); - - if (data->phy) { - usb_phy_shutdown(data->phy); - module_put(data->phy->dev->driver->owner); - } - - of_node_put(data->phy_np); - - clk_disable_unprepare(data->clk); - - platform_set_drvdata(pdev, NULL); - - return 0; -} - -static const struct of_device_id ci13xxx_imx_dt_ids[] = { - { .compatible = "fsl,imx27-usb", }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, ci13xxx_imx_dt_ids); - -static struct platform_driver ci13xxx_imx_driver = { - .probe = ci13xxx_imx_probe, - .remove = ci13xxx_imx_remove, - .driver = { - .name = "imx_usb", - .owner = THIS_MODULE, - .of_match_table = ci13xxx_imx_dt_ids, - }, -}; - -module_platform_driver(ci13xxx_imx_driver); - -MODULE_ALIAS("platform:imx-usb"); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("CI13xxx i.MX USB binding"); -MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); -MODULE_AUTHOR("Richard Zhao <richard.zhao@freescale.com>"); diff --git a/drivers/usb/chipidea/ci13xxx_imx.h b/drivers/usb/chipidea/ci13xxx_imx.h deleted file mode 100644 index 2e88accb3d6..00000000000 --- a/drivers/usb/chipidea/ci13xxx_imx.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2012 Freescale Semiconductor, Inc. - * - * The code contained herein is licensed under the GNU General Public - * License. You may obtain a copy of the GNU General Public License - * Version 2 or later at the following locations: - * - * http://www.opensource.org/licenses/gpl-license.html - * http://www.gnu.org/copyleft/gpl.html - */ - -/* Used to set SoC specific callbacks */ -struct usbmisc_ops { - /* It's called once when probe a usb device */ - int (*init)(struct device *dev); -}; - -struct usbmisc_usb_device { - struct device *dev; /* usb controller device */ - int index; - - int disable_oc:1; /* over current detect disabled */ -}; - -int usbmisc_set_ops(const struct usbmisc_ops *ops); -void usbmisc_unset_ops(const struct usbmisc_ops *ops); -int -usbmisc_get_init_data(struct device *dev, struct usbmisc_usb_device *usbdev); diff --git a/drivers/usb/chipidea/ci13xxx_msm.c b/drivers/usb/chipidea/ci13xxx_msm.c deleted file mode 100644 index 7d16681fd3d..00000000000 --- a/drivers/usb/chipidea/ci13xxx_msm.c +++ /dev/null @@ -1,99 +0,0 @@ -/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - */ - -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/pm_runtime.h> -#include <linux/usb/msm_hsusb_hw.h> -#include <linux/usb/ulpi.h> -#include <linux/usb/gadget.h> -#include <linux/usb/chipidea.h> - -#include "ci.h" - -#define MSM_USB_BASE (ci->hw_bank.abs) - -static void ci13xxx_msm_notify_event(struct ci13xxx *ci, unsigned event) -{ - struct device *dev = ci->gadget.dev.parent; - int val; - - switch (event) { - case CI13XXX_CONTROLLER_RESET_EVENT: - dev_dbg(dev, "CI13XXX_CONTROLLER_RESET_EVENT received\n"); - writel(0, USB_AHBBURST); - writel(0, USB_AHBMODE); - break; - case CI13XXX_CONTROLLER_STOPPED_EVENT: - dev_dbg(dev, "CI13XXX_CONTROLLER_STOPPED_EVENT received\n"); - /* - * Put the transceiver in non-driving mode. Otherwise host - * may not detect soft-disconnection. - */ - val = usb_phy_io_read(ci->transceiver, ULPI_FUNC_CTRL); - val &= ~ULPI_FUNC_CTRL_OPMODE_MASK; - val |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; - usb_phy_io_write(ci->transceiver, val, ULPI_FUNC_CTRL); - break; - default: - dev_dbg(dev, "unknown ci13xxx event\n"); - break; - } -} - -static struct ci13xxx_platform_data ci13xxx_msm_platdata = { - .name = "ci13xxx_msm", - .flags = CI13XXX_REGS_SHARED | - CI13XXX_REQUIRE_TRANSCEIVER | - CI13XXX_PULLUP_ON_VBUS | - CI13XXX_DISABLE_STREAMING, - - .notify_event = ci13xxx_msm_notify_event, -}; - -static int ci13xxx_msm_probe(struct platform_device *pdev) -{ - struct platform_device *plat_ci; - - dev_dbg(&pdev->dev, "ci13xxx_msm_probe\n"); - - plat_ci = ci13xxx_add_device(&pdev->dev, - pdev->resource, pdev->num_resources, - &ci13xxx_msm_platdata); - if (IS_ERR(plat_ci)) { - dev_err(&pdev->dev, "ci13xxx_add_device failed!\n"); - return PTR_ERR(plat_ci); - } - - platform_set_drvdata(pdev, plat_ci); - - pm_runtime_no_callbacks(&pdev->dev); - pm_runtime_enable(&pdev->dev); - - return 0; -} - -static int ci13xxx_msm_remove(struct platform_device *pdev) -{ - struct platform_device *plat_ci = platform_get_drvdata(pdev); - - pm_runtime_disable(&pdev->dev); - ci13xxx_remove_device(plat_ci); - - return 0; -} - -static struct platform_driver ci13xxx_msm_driver = { - .probe = ci13xxx_msm_probe, - .remove = ci13xxx_msm_remove, - .driver = { .name = "msm_hsusb", }, -}; - -module_platform_driver(ci13xxx_msm_driver); - -MODULE_ALIAS("platform:msm_hsusb"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c new file mode 100644 index 00000000000..2e58f8dfd31 --- /dev/null +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -0,0 +1,218 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright (C) 2012 Marek Vasut <marex@denx.de> + * on behalf of DENX Software Engineering GmbH + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/dma-mapping.h> +#include <linux/usb/chipidea.h> +#include <linux/clk.h> + +#include "ci.h" +#include "ci_hdrc_imx.h" + +#define CI_HDRC_IMX_IMX28_WRITE_FIX BIT(0) + +struct ci_hdrc_imx_platform_flag { + unsigned int flags; +}; + +static const struct ci_hdrc_imx_platform_flag imx27_usb_data = { +}; + +static const struct ci_hdrc_imx_platform_flag imx28_usb_data = { + .flags = CI_HDRC_IMX_IMX28_WRITE_FIX, +}; + +static const struct of_device_id ci_hdrc_imx_dt_ids[] = { + { .compatible = "fsl,imx28-usb", .data = &imx28_usb_data}, + { .compatible = "fsl,imx27-usb", .data = &imx27_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 imx_usbmisc_data *usbmisc_data; +}; + +/* Common functions shared by usbmisc drivers */ + +static struct imx_usbmisc_data *usbmisc_get_init_data(struct device *dev) +{ + 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_get_property(np, "fsl,usbmisc", NULL)) + 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]; + of_node_put(args.np); + + if (of_find_property(np, "disable-over-current", NULL)) + data->disable_oc = 1; + + if (of_find_property(np, "external-vbus-divider", NULL)) + data->evdo = 1; + + return data; +} + +/* End of common functions shared by usbmisc drivers*/ + +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_REQUIRE_TRANSCEIVER | + CI_HDRC_DISABLE_STREAMING, + }; + int ret; + const struct of_device_id *of_id = + of_match_device(ci_hdrc_imx_dt_ids, &pdev->dev); + const struct ci_hdrc_imx_platform_flag *imx_platform_flag = of_id->data; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, "Failed to allocate ci_hdrc-imx data!\n"); + return -ENOMEM; + } + + data->usbmisc_data = usbmisc_get_init_data(&pdev->dev); + if (IS_ERR(data->usbmisc_data)) + return PTR_ERR(data->usbmisc_data); + + data->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(data->clk)) { + dev_err(&pdev->dev, + "Failed to get clock, err=%ld\n", PTR_ERR(data->clk)); + return PTR_ERR(data->clk); + } + + ret = clk_prepare_enable(data->clk); + if (ret) { + dev_err(&pdev->dev, + "Failed to prepare or enable clock, err=%d\n", ret); + return ret; + } + + data->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "fsl,usbphy", 0); + if (IS_ERR(data->phy)) { + ret = PTR_ERR(data->phy); + goto err_clk; + } + + pdata.phy = data->phy; + + if (imx_platform_flag->flags & CI_HDRC_IMX_IMX28_WRITE_FIX) + pdata.flags |= CI_HDRC_IMX28_WRITE_FIX; + + ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + goto err_clk; + + if (data->usbmisc_data) { + ret = imx_usbmisc_init(data->usbmisc_data); + if (ret) { + dev_err(&pdev->dev, "usbmisc init failed, ret=%d\n", + ret); + goto err_clk; + } + } + + data->ci_pdev = ci_hdrc_add_device(&pdev->dev, + pdev->resource, pdev->num_resources, + &pdata); + if (IS_ERR(data->ci_pdev)) { + ret = PTR_ERR(data->ci_pdev); + dev_err(&pdev->dev, + "Can't register ci_hdrc platform device, err=%d\n", + ret); + goto err_clk; + } + + if (data->usbmisc_data) { + ret = imx_usbmisc_init_post(data->usbmisc_data); + if (ret) { + dev_err(&pdev->dev, "usbmisc post failed, ret=%d\n", + ret); + goto disable_device; + } + } + + platform_set_drvdata(pdev, data); + + pm_runtime_no_callbacks(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + return 0; + +disable_device: + ci_hdrc_remove_device(data->ci_pdev); +err_clk: + clk_disable_unprepare(data->clk); + return ret; +} + +static int ci_hdrc_imx_remove(struct platform_device *pdev) +{ + struct ci_hdrc_imx_data *data = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + ci_hdrc_remove_device(data->ci_pdev); + clk_disable_unprepare(data->clk); + + return 0; +} + +static struct platform_driver ci_hdrc_imx_driver = { + .probe = ci_hdrc_imx_probe, + .remove = ci_hdrc_imx_remove, + .driver = { + .name = "imx_usb", + .owner = THIS_MODULE, + .of_match_table = ci_hdrc_imx_dt_ids, + }, +}; + +module_platform_driver(ci_hdrc_imx_driver); + +MODULE_ALIAS("platform:imx-usb"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CI HDRC i.MX USB binding"); +MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); +MODULE_AUTHOR("Richard Zhao <richard.zhao@freescale.com>"); diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h new file mode 100644 index 00000000000..996ec93467b --- /dev/null +++ b/drivers/usb/chipidea/ci_hdrc_imx.h @@ -0,0 +1,25 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H +#define __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H + +struct imx_usbmisc_data { + int index; + + unsigned int disable_oc:1; /* over current detect disabled */ + unsigned int evdo:1; /* set external vbus divider option */ +}; + +int imx_usbmisc_init(struct imx_usbmisc_data *); +int imx_usbmisc_init_post(struct imx_usbmisc_data *); + +#endif /* __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H */ diff --git a/drivers/usb/chipidea/ci_hdrc_msm.c b/drivers/usb/chipidea/ci_hdrc_msm.c new file mode 100644 index 00000000000..d72b9d2de2c --- /dev/null +++ b/drivers/usb/chipidea/ci_hdrc_msm.c @@ -0,0 +1,121 @@ +/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/usb/msm_hsusb_hw.h> +#include <linux/usb/ulpi.h> +#include <linux/usb/gadget.h> +#include <linux/usb/chipidea.h> + +#include "ci.h" + +#define MSM_USB_BASE (ci->hw_bank.abs) + +static void ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event) +{ + struct device *dev = ci->gadget.dev.parent; + int val; + + switch (event) { + case CI_HDRC_CONTROLLER_RESET_EVENT: + dev_dbg(dev, "CI_HDRC_CONTROLLER_RESET_EVENT received\n"); + writel(0, USB_AHBBURST); + writel(0, USB_AHBMODE); + break; + case CI_HDRC_CONTROLLER_STOPPED_EVENT: + dev_dbg(dev, "CI_HDRC_CONTROLLER_STOPPED_EVENT received\n"); + /* + * Put the transceiver in non-driving mode. Otherwise host + * may not detect soft-disconnection. + */ + val = usb_phy_io_read(ci->transceiver, ULPI_FUNC_CTRL); + val &= ~ULPI_FUNC_CTRL_OPMODE_MASK; + val |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; + usb_phy_io_write(ci->transceiver, val, ULPI_FUNC_CTRL); + break; + default: + dev_dbg(dev, "unknown ci_hdrc event\n"); + break; + } +} + +static struct ci_hdrc_platform_data ci_hdrc_msm_platdata = { + .name = "ci_hdrc_msm", + .capoffset = DEF_CAPOFFSET, + .flags = CI_HDRC_REGS_SHARED | + CI_HDRC_REQUIRE_TRANSCEIVER | + CI_HDRC_DISABLE_STREAMING, + + .notify_event = ci_hdrc_msm_notify_event, +}; + +static int ci_hdrc_msm_probe(struct platform_device *pdev) +{ + struct platform_device *plat_ci; + struct usb_phy *phy; + + dev_dbg(&pdev->dev, "ci_hdrc_msm_probe\n"); + + /* + * OTG(PHY) driver takes care of PHY initialization, clock management, + * powering up VBUS, mapping of registers address space and power + * management. + */ + phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + ci_hdrc_msm_platdata.phy = phy; + + plat_ci = ci_hdrc_add_device(&pdev->dev, + pdev->resource, pdev->num_resources, + &ci_hdrc_msm_platdata); + if (IS_ERR(plat_ci)) { + dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n"); + return PTR_ERR(plat_ci); + } + + platform_set_drvdata(pdev, plat_ci); + + pm_runtime_no_callbacks(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static int ci_hdrc_msm_remove(struct platform_device *pdev) +{ + struct platform_device *plat_ci = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + ci_hdrc_remove_device(plat_ci); + + return 0; +} + +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_LICENSE("GPL v2"); diff --git a/drivers/usb/chipidea/ci13xxx_pci.c b/drivers/usb/chipidea/ci_hdrc_pci.c index 9b227e39299..241ae3444fd 100644 --- a/drivers/usb/chipidea/ci13xxx_pci.c +++ b/drivers/usb/chipidea/ci_hdrc_pci.c @@ -1,5 +1,5 @@ /* - * ci13xxx_pci.c - MIPS USB IP core family device controller + * ci_hdrc_pci.c - MIPS USB IP core family device controller * * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. * @@ -18,29 +18,29 @@ #include <linux/usb/chipidea.h> /* driver name */ -#define UDC_DRIVER_NAME "ci13xxx_pci" +#define UDC_DRIVER_NAME "ci_hdrc_pci" /****************************************************************************** * PCI block *****************************************************************************/ -struct ci13xxx_platform_data pci_platdata = { +static struct ci_hdrc_platform_data pci_platdata = { .name = UDC_DRIVER_NAME, .capoffset = DEF_CAPOFFSET, }; -struct ci13xxx_platform_data langwell_pci_platdata = { +static struct ci_hdrc_platform_data langwell_pci_platdata = { .name = UDC_DRIVER_NAME, .capoffset = 0, }; -struct ci13xxx_platform_data penwell_pci_platdata = { +static struct ci_hdrc_platform_data penwell_pci_platdata = { .name = UDC_DRIVER_NAME, .capoffset = 0, .power_budget = 200, }; /** - * ci13xxx_pci_probe: PCI probe + * ci_hdrc_pci_probe: PCI probe * @pdev: USB device controller being probed * @id: PCI hotplug ID connecting controller to UDC framework * @@ -48,10 +48,10 @@ struct ci13xxx_platform_data penwell_pci_platdata = { * 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 ci13xxx_pci_probe(struct pci_dev *pdev, +static int ci_hdrc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { - struct ci13xxx_platform_data *platdata = (void *)id->driver_data; + struct ci_hdrc_platform_data *platdata = (void *)id->driver_data; struct platform_device *plat_ci; struct resource res[3]; int retval = 0, nres = 2; @@ -61,17 +61,15 @@ static int ci13xxx_pci_probe(struct pci_dev *pdev, return -ENODEV; } - retval = pci_enable_device(pdev); + retval = pcim_enable_device(pdev); if (retval) - goto done; + return retval; if (!pdev->irq) { dev_err(&pdev->dev, "No IRQ, check BIOS/PCI setup!"); - retval = -ENODEV; - goto disable_device; + return -ENODEV; } - pci_set_power_state(pdev, PCI_D0); pci_set_master(pdev); pci_try_set_mwi(pdev); @@ -82,38 +80,30 @@ static int ci13xxx_pci_probe(struct pci_dev *pdev, res[1].start = pdev->irq; res[1].flags = IORESOURCE_IRQ; - plat_ci = ci13xxx_add_device(&pdev->dev, res, nres, platdata); + plat_ci = ci_hdrc_add_device(&pdev->dev, res, nres, platdata); if (IS_ERR(plat_ci)) { - dev_err(&pdev->dev, "ci13xxx_add_device failed!\n"); - retval = PTR_ERR(plat_ci); - goto disable_device; + dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n"); + return PTR_ERR(plat_ci); } pci_set_drvdata(pdev, plat_ci); return 0; - - disable_device: - pci_disable_device(pdev); - done: - return retval; } /** - * ci13xxx_pci_remove: PCI remove + * ci_hdrc_pci_remove: PCI remove * @pdev: USB Device Controller being removed * - * Reverses the effect of ci13xxx_pci_probe(), + * 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 ci13xxx_pci_remove(struct pci_dev *pdev) +static void ci_hdrc_pci_remove(struct pci_dev *pdev) { struct platform_device *plat_ci = pci_get_drvdata(pdev); - ci13xxx_remove_device(plat_ci); - pci_set_drvdata(pdev, NULL); - pci_disable_device(pdev); + ci_hdrc_remove_device(plat_ci); } /** @@ -122,7 +112,7 @@ static void ci13xxx_pci_remove(struct pci_dev *pdev) * * Check "pci.h" for details */ -static DEFINE_PCI_DEVICE_TABLE(ci13xxx_pci_id_table) = { +static const struct pci_device_id ci_hdrc_pci_id_table[] = { { PCI_DEVICE(0x153F, 0x1004), .driver_data = (kernel_ulong_t)&pci_platdata, @@ -139,20 +129,26 @@ static DEFINE_PCI_DEVICE_TABLE(ci13xxx_pci_id_table) = { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0829), .driver_data = (kernel_ulong_t)&penwell_pci_platdata, }, - { 0, 0, 0, 0, 0, 0, 0 /* end: all zeroes */ } + { + /* Intel Clovertrail */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xe006), + .driver_data = (kernel_ulong_t)&penwell_pci_platdata, + }, + { 0 } /* end: all zeroes */ }; -MODULE_DEVICE_TABLE(pci, ci13xxx_pci_id_table); +MODULE_DEVICE_TABLE(pci, ci_hdrc_pci_id_table); -static struct pci_driver ci13xxx_pci_driver = { +static struct pci_driver ci_hdrc_pci_driver = { .name = UDC_DRIVER_NAME, - .id_table = ci13xxx_pci_id_table, - .probe = ci13xxx_pci_probe, - .remove = ci13xxx_pci_remove, + .id_table = ci_hdrc_pci_id_table, + .probe = ci_hdrc_pci_probe, + .remove = ci_hdrc_pci_remove, }; -module_pci_driver(ci13xxx_pci_driver); +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_VERSION("June 2008"); +MODULE_ALIAS("platform:ci13xxx_pci"); diff --git a/drivers/usb/chipidea/ci_hdrc_zevio.c b/drivers/usb/chipidea/ci_hdrc_zevio.c new file mode 100644 index 00000000000..3bf6489ef5e --- /dev/null +++ b/drivers/usb/chipidea/ci_hdrc_zevio.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 Daniel Tang <tangrs@tangrs.id.au> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * Based off drivers/usb/chipidea/ci_hdrc_msm.c + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/usb/gadget.h> +#include <linux/usb/chipidea.h> + +#include "ci.h" + +static struct ci_hdrc_platform_data ci_hdrc_zevio_platdata = { + .name = "ci_hdrc_zevio", + .flags = CI_HDRC_REGS_SHARED, + .capoffset = DEF_CAPOFFSET, +}; + +static int ci_hdrc_zevio_probe(struct platform_device *pdev) +{ + struct platform_device *ci_pdev; + + dev_dbg(&pdev->dev, "ci_hdrc_zevio_probe\n"); + + ci_pdev = ci_hdrc_add_device(&pdev->dev, + pdev->resource, pdev->num_resources, + &ci_hdrc_zevio_platdata); + + if (IS_ERR(ci_pdev)) { + dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n"); + return PTR_ERR(ci_pdev); + } + + platform_set_drvdata(pdev, ci_pdev); + + return 0; +} + +static int ci_hdrc_zevio_remove(struct platform_device *pdev) +{ + struct platform_device *ci_pdev = platform_get_drvdata(pdev); + + ci_hdrc_remove_device(ci_pdev); + + return 0; +} + +static const struct of_device_id ci_hdrc_zevio_dt_ids[] = { + { .compatible = "lsi,zevio-usb", }, + { /* sentinel */ } +}; + +static struct platform_driver ci_hdrc_zevio_driver = { + .probe = ci_hdrc_zevio_probe, + .remove = ci_hdrc_zevio_remove, + .driver = { + .name = "zevio_usb", + .owner = THIS_MODULE, + .of_match_table = ci_hdrc_zevio_dt_ids, + }, +}; + +MODULE_DEVICE_TABLE(of, ci_hdrc_zevio_dt_ids); +module_platform_driver(ci_hdrc_zevio_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index aebf695a934..619d13e2999 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -23,7 +23,7 @@ * - BUS: bus glue code, bus abstraction layer * * Compile Options - * - CONFIG_USB_GADGET_DEBUG_FILES: enable debug facilities + * - CONFIG_USB_CHIPIDEA_DEBUG: enable debug facilities * - STALL_IN: non-empty bulk-in pipes cannot be halted * if defined mass storage compliance succeeds but with warnings * => case 4: Hi > Dn @@ -42,24 +42,16 @@ * - Not Supported: 15 & 16 (ISO) * * TODO List - * - OTG - * - Isochronous & Interrupt Traffic - * - Handle requests which spawns into several TDs - * - GET_STATUS(device) - always reports 0 - * - Gadget API (majority of optional features) * - Suspend & Remote Wakeup */ #include <linux/delay.h> #include <linux/device.h> -#include <linux/dmapool.h> #include <linux/dma-mapping.h> -#include <linux/init.h> #include <linux/platform_device.h> #include <linux/module.h> #include <linux/idr.h> #include <linux/interrupt.h> #include <linux/io.h> -#include <linux/irq.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/pm_runtime.h> @@ -67,69 +59,68 @@ #include <linux/usb/gadget.h> #include <linux/usb/otg.h> #include <linux/usb/chipidea.h> +#include <linux/usb/of.h> +#include <linux/of.h> +#include <linux/phy.h> +#include <linux/regulator/consumer.h> #include "ci.h" #include "udc.h" #include "bits.h" #include "host.h" #include "debug.h" +#include "otg.h" +#include "otg_fsm.h" /* Controller register map */ -static uintptr_t ci_regs_nolpm[] = { - [CAP_CAPLENGTH] = 0x000UL, - [CAP_HCCPARAMS] = 0x008UL, - [CAP_DCCPARAMS] = 0x024UL, - [CAP_TESTMODE] = 0x038UL, - [OP_USBCMD] = 0x000UL, - [OP_USBSTS] = 0x004UL, - [OP_USBINTR] = 0x008UL, - [OP_DEVICEADDR] = 0x014UL, - [OP_ENDPTLISTADDR] = 0x018UL, - [OP_PORTSC] = 0x044UL, - [OP_DEVLC] = 0x084UL, - [OP_OTGSC] = 0x064UL, - [OP_USBMODE] = 0x068UL, - [OP_ENDPTSETUPSTAT] = 0x06CUL, - [OP_ENDPTPRIME] = 0x070UL, - [OP_ENDPTFLUSH] = 0x074UL, - [OP_ENDPTSTAT] = 0x078UL, - [OP_ENDPTCOMPLETE] = 0x07CUL, - [OP_ENDPTCTRL] = 0x080UL, +static const u8 ci_regs_nolpm[] = { + [CAP_CAPLENGTH] = 0x00U, + [CAP_HCCPARAMS] = 0x08U, + [CAP_DCCPARAMS] = 0x24U, + [CAP_TESTMODE] = 0x38U, + [OP_USBCMD] = 0x00U, + [OP_USBSTS] = 0x04U, + [OP_USBINTR] = 0x08U, + [OP_DEVICEADDR] = 0x14U, + [OP_ENDPTLISTADDR] = 0x18U, + [OP_PORTSC] = 0x44U, + [OP_DEVLC] = 0x84U, + [OP_OTGSC] = 0x64U, + [OP_USBMODE] = 0x68U, + [OP_ENDPTSETUPSTAT] = 0x6CU, + [OP_ENDPTPRIME] = 0x70U, + [OP_ENDPTFLUSH] = 0x74U, + [OP_ENDPTSTAT] = 0x78U, + [OP_ENDPTCOMPLETE] = 0x7CU, + [OP_ENDPTCTRL] = 0x80U, }; -static uintptr_t ci_regs_lpm[] = { - [CAP_CAPLENGTH] = 0x000UL, - [CAP_HCCPARAMS] = 0x008UL, - [CAP_DCCPARAMS] = 0x024UL, - [CAP_TESTMODE] = 0x0FCUL, - [OP_USBCMD] = 0x000UL, - [OP_USBSTS] = 0x004UL, - [OP_USBINTR] = 0x008UL, - [OP_DEVICEADDR] = 0x014UL, - [OP_ENDPTLISTADDR] = 0x018UL, - [OP_PORTSC] = 0x044UL, - [OP_DEVLC] = 0x084UL, - [OP_OTGSC] = 0x0C4UL, - [OP_USBMODE] = 0x0C8UL, - [OP_ENDPTSETUPSTAT] = 0x0D8UL, - [OP_ENDPTPRIME] = 0x0DCUL, - [OP_ENDPTFLUSH] = 0x0E0UL, - [OP_ENDPTSTAT] = 0x0E4UL, - [OP_ENDPTCOMPLETE] = 0x0E8UL, - [OP_ENDPTCTRL] = 0x0ECUL, +static const u8 ci_regs_lpm[] = { + [CAP_CAPLENGTH] = 0x00U, + [CAP_HCCPARAMS] = 0x08U, + [CAP_DCCPARAMS] = 0x24U, + [CAP_TESTMODE] = 0xFCU, + [OP_USBCMD] = 0x00U, + [OP_USBSTS] = 0x04U, + [OP_USBINTR] = 0x08U, + [OP_DEVICEADDR] = 0x14U, + [OP_ENDPTLISTADDR] = 0x18U, + [OP_PORTSC] = 0x44U, + [OP_DEVLC] = 0x84U, + [OP_OTGSC] = 0xC4U, + [OP_USBMODE] = 0xC8U, + [OP_ENDPTSETUPSTAT] = 0xD8U, + [OP_ENDPTPRIME] = 0xDCU, + [OP_ENDPTFLUSH] = 0xE0U, + [OP_ENDPTSTAT] = 0xE4U, + [OP_ENDPTCOMPLETE] = 0xE8U, + [OP_ENDPTCTRL] = 0xECU, }; -static int hw_alloc_regmap(struct ci13xxx *ci, bool is_lpm) +static int hw_alloc_regmap(struct ci_hdrc *ci, bool is_lpm) { int i; - kfree(ci->hw_bank.regmap); - - ci->hw_bank.regmap = kzalloc((OP_LAST + 1) * sizeof(void *), - GFP_KERNEL); - if (!ci->hw_bank.regmap) - return -ENOMEM; - for (i = 0; i < OP_ENDPTCTRL; i++) ci->hw_bank.regmap[i] = (i <= CAP_LAST ? ci->hw_bank.cap : ci->hw_bank.op) + @@ -146,19 +137,39 @@ static int hw_alloc_regmap(struct ci13xxx *ci, bool is_lpm) } /** + * hw_read_intr_enable: returns interrupt enable register + * + * This function returns register data + */ +u32 hw_read_intr_enable(struct ci_hdrc *ci) +{ + return hw_read(ci, OP_USBINTR, ~0); +} + +/** + * hw_read_intr_status: returns interrupt status register + * + * This function returns register data + */ +u32 hw_read_intr_status(struct ci_hdrc *ci) +{ + return hw_read(ci, OP_USBSTS, ~0); +} + +/** * hw_port_test_set: writes port test mode (execute without interruption) * @mode: new value * * This function returns an error code */ -int hw_port_test_set(struct ci13xxx *ci, u8 mode) +int hw_port_test_set(struct ci_hdrc *ci, u8 mode) { const u8 TEST_MODE_MAX = 7; if (mode > TEST_MODE_MAX) return -EINVAL; - hw_write(ci, OP_PORTSC, PORTSC_PTC, mode << ffs_nr(PORTSC_PTC)); + hw_write(ci, OP_PORTSC, PORTSC_PTC, mode << __ffs(PORTSC_PTC)); return 0; } @@ -167,12 +178,32 @@ int hw_port_test_set(struct ci13xxx *ci, u8 mode) * * This function returns port test mode value */ -u8 hw_port_test_get(struct ci13xxx *ci) +u8 hw_port_test_get(struct ci_hdrc *ci) { - return hw_read(ci, OP_PORTSC, PORTSC_PTC) >> ffs_nr(PORTSC_PTC); + return hw_read(ci, OP_PORTSC, PORTSC_PTC) >> __ffs(PORTSC_PTC); } -static int hw_device_init(struct ci13xxx *ci, void __iomem *base) +/* The PHY enters/leaves low power mode */ +static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) +{ + enum ci_hw_regs reg = ci->hw_bank.lpm ? OP_DEVLC : OP_PORTSC; + bool lpm = !!(hw_read(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm))); + + if (enable && !lpm) { + hw_write(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm), + PORTSC_PHCD(ci->hw_bank.lpm)); + } else if (!enable && lpm) { + hw_write(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm), + 0); + /* + * the PHY needs some time (less + * than 1ms) to leave low power mode. + */ + usleep_range(1000, 1100); + } +} + +static int hw_device_init(struct ci_hdrc *ci, void __iomem *base) { u32 reg; @@ -181,24 +212,33 @@ static int hw_device_init(struct ci13xxx *ci, void __iomem *base) ci->hw_bank.cap = ci->hw_bank.abs; ci->hw_bank.cap += ci->platdata->capoffset; - ci->hw_bank.op = ci->hw_bank.cap + ioread8(ci->hw_bank.cap); + ci->hw_bank.op = ci->hw_bank.cap + (ioread32(ci->hw_bank.cap) & 0xff); hw_alloc_regmap(ci, false); reg = hw_read(ci, CAP_HCCPARAMS, HCCPARAMS_LEN) >> - ffs_nr(HCCPARAMS_LEN); + __ffs(HCCPARAMS_LEN); ci->hw_bank.lpm = reg; - hw_alloc_regmap(ci, !!reg); + if (reg) + hw_alloc_regmap(ci, !!reg); ci->hw_bank.size = ci->hw_bank.op - ci->hw_bank.abs; ci->hw_bank.size += OP_LAST; ci->hw_bank.size /= sizeof(u32); reg = hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_DEN) >> - ffs_nr(DCCPARAMS_DEN); + __ffs(DCCPARAMS_DEN); ci->hw_ep_max = reg * 2; /* cache hw ENDPT_MAX */ if (ci->hw_ep_max > ENDPT_MAX) return -ENODEV; + ci_hdrc_enter_lpm(ci, false); + + /* Disable all interrupts bits */ + hw_write(ci, OP_USBINTR, 0xffffffff, 0); + + /* Clear all interrupts status bits*/ + hw_write(ci, OP_USBSTS, 0xffffffff, 0xffffffff); + dev_dbg(ci->dev, "ChipIdea HDRC found, lpm: %d; cap: %p op: %p\n", ci->hw_bank.lpm, ci->hw_bank.cap, ci->hw_bank.op); @@ -211,13 +251,87 @@ static int hw_device_init(struct ci13xxx *ci, void __iomem *base) return 0; } +static void hw_phymode_configure(struct ci_hdrc *ci) +{ + u32 portsc, lpm, sts = 0; + + switch (ci->platdata->phy_mode) { + case USBPHY_INTERFACE_MODE_UTMI: + portsc = PORTSC_PTS(PTS_UTMI); + lpm = DEVLC_PTS(PTS_UTMI); + break; + case USBPHY_INTERFACE_MODE_UTMIW: + portsc = PORTSC_PTS(PTS_UTMI) | PORTSC_PTW; + lpm = DEVLC_PTS(PTS_UTMI) | DEVLC_PTW; + break; + case USBPHY_INTERFACE_MODE_ULPI: + portsc = PORTSC_PTS(PTS_ULPI); + lpm = DEVLC_PTS(PTS_ULPI); + break; + case USBPHY_INTERFACE_MODE_SERIAL: + portsc = PORTSC_PTS(PTS_SERIAL); + lpm = DEVLC_PTS(PTS_SERIAL); + sts = 1; + break; + case USBPHY_INTERFACE_MODE_HSIC: + portsc = PORTSC_PTS(PTS_HSIC); + lpm = DEVLC_PTS(PTS_HSIC); + break; + default: + return; + } + + if (ci->hw_bank.lpm) { + hw_write(ci, OP_DEVLC, DEVLC_PTS(7) | DEVLC_PTW, lpm); + if (sts) + hw_write(ci, OP_DEVLC, DEVLC_STS, DEVLC_STS); + } else { + hw_write(ci, OP_PORTSC, PORTSC_PTS(7) | PORTSC_PTW, portsc); + if (sts) + hw_write(ci, OP_PORTSC, PORTSC_STS, PORTSC_STS); + } +} + +/** + * ci_usb_phy_init: initialize phy according to different phy type + * @ci: the controller + * + * This function returns an error code if usb_phy_init has failed + */ +static int ci_usb_phy_init(struct ci_hdrc *ci) +{ + int ret; + + switch (ci->platdata->phy_mode) { + case USBPHY_INTERFACE_MODE_UTMI: + case USBPHY_INTERFACE_MODE_UTMIW: + case USBPHY_INTERFACE_MODE_HSIC: + ret = usb_phy_init(ci->transceiver); + if (ret) + return ret; + hw_phymode_configure(ci); + break; + case USBPHY_INTERFACE_MODE_ULPI: + case USBPHY_INTERFACE_MODE_SERIAL: + hw_phymode_configure(ci); + ret = usb_phy_init(ci->transceiver); + if (ret) + return ret; + break; + default: + ret = usb_phy_init(ci->transceiver); + } + + return ret; +} + /** * hw_device_reset: resets chip (execute without interruption) * @ci: the controller * * This function returns an error code */ -int hw_device_reset(struct ci13xxx *ci, u32 mode) +int hw_device_reset(struct ci_hdrc *ci, u32 mode) { /* should flush & stop before reset */ hw_write(ci, OP_ENDPTFLUSH, ~0, ~0); @@ -227,14 +341,20 @@ int hw_device_reset(struct ci13xxx *ci, u32 mode) while (hw_read(ci, OP_USBCMD, USBCMD_RST)) udelay(10); /* not RTOS friendly */ - if (ci->platdata->notify_event) ci->platdata->notify_event(ci, - CI13XXX_CONTROLLER_RESET_EVENT); + CI_HDRC_CONTROLLER_RESET_EVENT); - if (ci->platdata->flags & CI13XXX_DISABLE_STREAMING) + if (ci->platdata->flags & CI_HDRC_DISABLE_STREAMING) hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS); + if (ci->platdata->flags & CI_HDRC_FORCE_FULLSPEED) { + if (ci->hw_bank.lpm) + hw_write(ci, OP_DEVLC, DEVLC_PFSC, DEVLC_PFSC); + else + hw_write(ci, OP_PORTSC, PORTSC_PFSC, PORTSC_PFSC); + } + /* USBMODE should be configured step by step */ hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE); hw_write(ci, OP_USBMODE, USBMODE_CM, mode); @@ -251,101 +371,129 @@ int hw_device_reset(struct ci13xxx *ci, u32 mode) } /** - * ci_otg_role - pick role based on ID pin state + * hw_wait_reg: wait the register value + * + * Sometimes, it needs to wait register value before going on. + * Eg, when switch to device mode, the vbus value should be lower + * than OTGSC_BSV before connects to host. + * * @ci: the controller + * @reg: register index + * @mask: mast bit + * @value: the bit value to wait + * @timeout_ms: timeout in millisecond + * + * This function returns an error code if timeout */ -static enum ci_role ci_otg_role(struct ci13xxx *ci) -{ - u32 sts = hw_read(ci, OP_OTGSC, ~0); - enum ci_role role = sts & OTGSC_ID - ? CI_ROLE_GADGET - : CI_ROLE_HOST; - - return role; -} - -/** - * ci_role_work - perform role changing based on ID pin - * @work: work struct - */ -static void ci_role_work(struct work_struct *work) +int hw_wait_reg(struct ci_hdrc *ci, enum ci_hw_regs reg, u32 mask, + u32 value, unsigned int timeout_ms) { - struct ci13xxx *ci = container_of(work, struct ci13xxx, work); - enum ci_role 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); - - ci_role_stop(ci); - ci_role_start(ci, role); - enable_irq(ci->irq); + unsigned long elapse = jiffies + msecs_to_jiffies(timeout_ms); + + while (hw_read(ci, reg, mask) != value) { + if (time_after(jiffies, elapse)) { + dev_err(ci->dev, "timeout waiting for %08x in %d\n", + mask, reg); + return -ETIMEDOUT; + } + msleep(20); } -} - -static ssize_t show_role(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct ci13xxx *ci = dev_get_drvdata(dev); - - return sprintf(buf, "%s\n", ci_role(ci)->name); -} - -static ssize_t store_role(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct ci13xxx *ci = dev_get_drvdata(dev); - enum ci_role role; - int ret; - for (role = CI_ROLE_HOST; role < CI_ROLE_END; role++) - if (ci->roles[role] && !strcmp(buf, ci->roles[role]->name)) - break; - - if (role == CI_ROLE_END || role == ci->role) - return -EINVAL; - - ci_role_stop(ci); - ret = ci_role_start(ci, role); - if (ret) - return ret; - - return count; + return 0; } -static DEVICE_ATTR(role, S_IRUSR | S_IWUSR, show_role, store_role); - static irqreturn_t ci_irq(int irq, void *data) { - struct ci13xxx *ci = data; + struct ci_hdrc *ci = data; irqreturn_t ret = IRQ_NONE; u32 otgsc = 0; - if (ci->is_otg) - otgsc = hw_read(ci, OP_OTGSC, ~0); + if (ci->is_otg) { + otgsc = hw_read_otgsc(ci, ~0); + if (ci_otg_is_fsm_mode(ci)) { + ret = ci_otg_fsm_irq(ci); + if (ret == IRQ_HANDLED) + return ret; + } + } + /* + * Handle id change interrupt, it indicates device/host function + * switch. + */ + if (ci->is_otg && (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) { + ci->id_event = true; + /* Clear ID change irq status */ + hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS); + ci_otg_queue_work(ci); + return IRQ_HANDLED; + } + + /* + * Handle vbus change interrupt, it indicates device connection + * and disconnection events. + */ + if (ci->is_otg && (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) { + ci->b_sess_valid_event = true; + /* Clear BSV irq */ + hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS); + ci_otg_queue_work(ci); + return IRQ_HANDLED; + } + + /* Handle device/host interrupt */ if (ci->role != CI_ROLE_END) ret = ci_role(ci)->irq(ci); - if (ci->is_otg && (otgsc & OTGSC_IDIS)) { - hw_write(ci, OP_OTGSC, OTGSC_IDIS, OTGSC_IDIS); - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); - ret = IRQ_HANDLED; + return ret; +} + +static int ci_get_platdata(struct device *dev, + struct ci_hdrc_platform_data *platdata) +{ + if (!platdata->phy_mode) + platdata->phy_mode = of_usb_get_phy_mode(dev->of_node); + + if (!platdata->dr_mode) + platdata->dr_mode = of_usb_get_dr_mode(dev->of_node); + + if (platdata->dr_mode == USB_DR_MODE_UNKNOWN) + platdata->dr_mode = USB_DR_MODE_OTG; + + if (platdata->dr_mode != USB_DR_MODE_PERIPHERAL) { + /* Get the vbus regulator */ + platdata->reg_vbus = devm_regulator_get(dev, "vbus"); + if (PTR_ERR(platdata->reg_vbus) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (PTR_ERR(platdata->reg_vbus) == -ENODEV) { + /* no vbus regualator is needed */ + platdata->reg_vbus = NULL; + } else if (IS_ERR(platdata->reg_vbus)) { + dev_err(dev, "Getting regulator error: %ld\n", + PTR_ERR(platdata->reg_vbus)); + return PTR_ERR(platdata->reg_vbus); + } } - return ret; + if (of_usb_get_maximum_speed(dev->of_node) == USB_SPEED_FULL) + platdata->flags |= CI_HDRC_FORCE_FULLSPEED; + + return 0; } static DEFINE_IDA(ci_ida); -struct platform_device *ci13xxx_add_device(struct device *dev, +struct platform_device *ci_hdrc_add_device(struct device *dev, struct resource *res, int nres, - struct ci13xxx_platform_data *platdata) + struct ci_hdrc_platform_data *platdata) { struct platform_device *pdev; int id, ret; + ret = ci_get_platdata(dev, platdata); + if (ret) + return ERR_PTR(ret); + id = ida_simple_get(&ci_ida, 0, 0, GFP_KERNEL); if (id < 0) return ERR_PTR(id); @@ -381,40 +529,54 @@ put_id: ida_simple_remove(&ci_ida, id); return ERR_PTR(ret); } -EXPORT_SYMBOL_GPL(ci13xxx_add_device); +EXPORT_SYMBOL_GPL(ci_hdrc_add_device); -void ci13xxx_remove_device(struct platform_device *pdev) +void ci_hdrc_remove_device(struct platform_device *pdev) { int id = pdev->id; platform_device_unregister(pdev); ida_simple_remove(&ci_ida, id); } -EXPORT_SYMBOL_GPL(ci13xxx_remove_device); +EXPORT_SYMBOL_GPL(ci_hdrc_remove_device); + +static inline void ci_role_destroy(struct ci_hdrc *ci) +{ + ci_hdrc_gadget_destroy(ci); + ci_hdrc_host_destroy(ci); + if (ci->is_otg) + ci_hdrc_otg_destroy(ci); +} + +static void ci_get_otg_capable(struct ci_hdrc *ci) +{ + if (ci->platdata->flags & CI_HDRC_DUAL_ROLE_NOT_OTG) + ci->is_otg = false; + else + ci->is_otg = (hw_read(ci, CAP_DCCPARAMS, + DCCPARAMS_DC | DCCPARAMS_HC) + == (DCCPARAMS_DC | DCCPARAMS_HC)); + if (ci->is_otg) + dev_dbg(ci->dev, "It is OTG capable controller\n"); +} static int ci_hdrc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct ci13xxx *ci; + struct ci_hdrc *ci; struct resource *res; void __iomem *base; int ret; + enum usb_dr_mode dr_mode; - if (!dev->platform_data) { + if (!dev_get_platdata(dev)) { dev_err(dev, "platform data missing\n"); return -ENODEV; } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "missing resource\n"); - return -ENODEV; - } - - base = devm_request_and_ioremap(dev, res); - if (!res) { - dev_err(dev, "can't request and ioremap resource\n"); - return -ENOMEM; - } + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); ci = devm_kzalloc(dev, sizeof(*ci), GFP_KERNEL); if (!ci) { @@ -423,11 +585,9 @@ static int ci_hdrc_probe(struct platform_device *pdev) } ci->dev = dev; - ci->platdata = dev->platform_data; - if (ci->platdata->phy) - ci->transceiver = ci->platdata->phy; - else - ci->global_phy = true; + ci->platdata = dev_get_platdata(dev); + ci->imx28_write_fix = !!(ci->platdata->flags & + CI_HDRC_IMX28_WRITE_FIX); ret = hw_device_init(ci, base); if (ret < 0) { @@ -435,52 +595,110 @@ static int ci_hdrc_probe(struct platform_device *pdev) return -ENODEV; } + if (ci->platdata->phy) + ci->transceiver = ci->platdata->phy; + else + ci->transceiver = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + + if (IS_ERR(ci->transceiver)) { + ret = PTR_ERR(ci->transceiver); + /* + * if -ENXIO is returned, it means PHY layer wasn't + * enabled, so it makes no sense to return -EPROBE_DEFER + * in that case, since no PHY driver will ever probe. + */ + if (ret == -ENXIO) + return ret; + + dev_err(dev, "no usb2 phy configured\n"); + return -EPROBE_DEFER; + } + + ret = ci_usb_phy_init(ci); + if (ret) { + dev_err(dev, "unable to init phy: %d\n", ret); + return ret; + } else { + /* + * The delay to sync PHY's status, the maximum delay is + * 2ms since the otgsc uses 1ms timer to debounce the + * PHY's input + */ + usleep_range(2000, 2500); + } + ci->hw_bank.phys = res->start; ci->irq = platform_get_irq(pdev, 0); if (ci->irq < 0) { dev_err(dev, "missing IRQ\n"); - return -ENODEV; + ret = ci->irq; + goto deinit_phy; } - INIT_WORK(&ci->work, ci_role_work); - ci->wq = create_singlethread_workqueue("ci_otg"); - if (!ci->wq) { - dev_err(dev, "can't create workqueue\n"); - return -ENODEV; - } + ci_get_otg_capable(ci); + dr_mode = ci->platdata->dr_mode; /* initialize role(s) before the interrupt is requested */ - ret = ci_hdrc_host_init(ci); - if (ret) - dev_info(dev, "doesn't support host\n"); + if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { + ret = ci_hdrc_host_init(ci); + if (ret) + dev_info(dev, "doesn't support host\n"); + } - ret = ci_hdrc_gadget_init(ci); - if (ret) - dev_info(dev, "doesn't support gadget\n"); + if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) { + ret = ci_hdrc_gadget_init(ci); + if (ret) + dev_info(dev, "doesn't support gadget\n"); + } if (!ci->roles[CI_ROLE_HOST] && !ci->roles[CI_ROLE_GADGET]) { dev_err(dev, "no supported roles\n"); ret = -ENODEV; - goto rm_wq; + goto deinit_phy; + } + + if (ci->is_otg) { + /* Disable and clear all OTG irq */ + hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, + OTGSC_INT_STATUS_BITS); + ret = ci_hdrc_otg_init(ci); + if (ret) { + dev_err(dev, "init otg fails, ret = %d\n", ret); + goto stop; + } } if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { - ci->is_otg = true; - /* ID pin needs 1ms debouce time, we delay 2ms for safe */ - mdelay(2); - ci->role = ci_otg_role(ci); + if (ci->is_otg) { + ci->role = ci_otg_role(ci); + /* Enable ID change irq */ + hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); + } else { + /* + * If the controller is not OTG capable, but support + * role switch, the defalt role is gadget, and the + * user can switch it through debugfs. + */ + ci->role = CI_ROLE_GADGET; + } } else { ci->role = ci->roles[CI_ROLE_HOST] ? CI_ROLE_HOST : CI_ROLE_GADGET; } - ret = ci_role_start(ci, ci->role); - if (ret) { - dev_err(dev, "can't start %s role\n", ci_role(ci)->name); - ret = -ENODEV; - goto rm_wq; + /* only update vbus status for peripheral */ + if (ci->role == CI_ROLE_GADGET) + ci_handle_vbus_change(ci); + + if (!ci_otg_is_fsm_mode(ci)) { + ret = ci_role_start(ci, ci->role); + if (ret) { + dev_err(dev, "can't start %s role\n", + ci_role(ci)->name); + goto stop; + } } platform_set_drvdata(pdev, ci); @@ -489,35 +707,32 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (ret) goto stop; - ret = device_create_file(dev, &dev_attr_role); - if (ret) - goto rm_attr; - - if (ci->is_otg) - hw_write(ci, OP_OTGSC, OTGSC_IDIE, OTGSC_IDIE); + if (ci_otg_is_fsm_mode(ci)) + ci_hdrc_otg_fsm_start(ci); - return ret; + ret = dbg_create_files(ci); + if (!ret) + return 0; -rm_attr: - device_remove_file(dev, &dev_attr_role); + free_irq(ci->irq, ci); stop: - ci_role_stop(ci); -rm_wq: - flush_workqueue(ci->wq); - destroy_workqueue(ci->wq); + ci_role_destroy(ci); +deinit_phy: + usb_phy_shutdown(ci->transceiver); return ret; } static int ci_hdrc_remove(struct platform_device *pdev) { - struct ci13xxx *ci = platform_get_drvdata(pdev); + struct ci_hdrc *ci = platform_get_drvdata(pdev); - flush_workqueue(ci->wq); - destroy_workqueue(ci->wq); - device_remove_file(ci->dev, &dev_attr_role); + dbg_remove_files(ci); free_irq(ci->irq, ci); - ci_role_stop(ci); + ci_role_destroy(ci); + ci_hdrc_enter_lpm(ci, true); + usb_phy_shutdown(ci->transceiver); + kfree(ci->hw_bank.regmap); return 0; } @@ -527,13 +742,13 @@ static struct platform_driver ci_hdrc_driver = { .remove = ci_hdrc_remove, .driver = { .name = "ci_hdrc", + .owner = THIS_MODULE, }, }; module_platform_driver(ci_hdrc_driver); MODULE_ALIAS("platform:ci_hdrc"); -MODULE_ALIAS("platform:ci13xxx"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("David Lopo <dlopo@chipidea.mips.com>"); MODULE_DESCRIPTION("ChipIdea HDRC Driver"); diff --git a/drivers/usb/chipidea/debug.c b/drivers/usb/chipidea/debug.c index 3bc244d2636..7cccab6ff30 100644 --- a/drivers/usb/chipidea/debug.c +++ b/drivers/usb/chipidea/debug.c @@ -1,801 +1,441 @@ -#include <linux/delay.h> -#include <linux/device.h> -#include <linux/dmapool.h> -#include <linux/dma-mapping.h> -#include <linux/init.h> -#include <linux/platform_device.h> -#include <linux/module.h> -#include <linux/interrupt.h> -#include <linux/io.h> -#include <linux/irq.h> #include <linux/kernel.h> -#include <linux/slab.h> -#include <linux/pm_runtime.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/chipidea.h> +#include <linux/usb/otg-fsm.h> #include "ci.h" #include "udc.h" #include "bits.h" #include "debug.h" - -/* Interrupt statistics */ -#define ISR_MASK 0x1F -static struct isr_statistics { - u32 test; - u32 ui; - u32 uei; - u32 pci; - u32 uri; - u32 sli; - u32 none; - struct { - u32 cnt; - u32 buf[ISR_MASK+1]; - u32 idx; - } hndl; -} isr_statistics; - -void dbg_interrupt(u32 intmask) -{ - if (!intmask) { - isr_statistics.none++; - return; - } - - isr_statistics.hndl.buf[isr_statistics.hndl.idx++] = intmask; - isr_statistics.hndl.idx &= ISR_MASK; - isr_statistics.hndl.cnt++; - - if (USBi_URI & intmask) - isr_statistics.uri++; - if (USBi_PCI & intmask) - isr_statistics.pci++; - if (USBi_UEI & intmask) - isr_statistics.uei++; - if (USBi_UI & intmask) - isr_statistics.ui++; - if (USBi_SLI & intmask) - isr_statistics.sli++; -} +#include "otg.h" /** - * hw_register_read: reads all device registers (execute without interruption) - * @buf: destination buffer - * @size: buffer size - * - * This function returns number of registers read + * ci_device_show: prints information about device capabilities and status */ -static size_t hw_register_read(struct ci13xxx *ci, u32 *buf, size_t size) +static int ci_device_show(struct seq_file *s, void *data) { - unsigned i; + struct ci_hdrc *ci = s->private; + struct usb_gadget *gadget = &ci->gadget; - if (size > ci->hw_bank.size) - size = ci->hw_bank.size; + 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; - for (i = 0; i < size; i++) - buf[i] = hw_read(ci, i * sizeof(u32), ~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 size; + return 0; +} + +static int ci_device_open(struct inode *inode, struct file *file) +{ + return single_open(file, ci_device_show, inode->i_private); } +static const struct file_operations ci_device_fops = { + .open = ci_device_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + /** - * hw_register_write: writes to register - * @addr: register address - * @data: register value - * - * This function returns an error code + * ci_port_test_show: reads port test mode */ -static int hw_register_write(struct ci13xxx *ci, u16 addr, u32 data) +static int ci_port_test_show(struct seq_file *s, void *data) { - /* align */ - addr /= sizeof(u32); + struct ci_hdrc *ci = s->private; + unsigned long flags; + unsigned mode; - if (addr >= ci->hw_bank.size) - return -EINVAL; + spin_lock_irqsave(&ci->lock, flags); + mode = hw_port_test_get(ci); + spin_unlock_irqrestore(&ci->lock, flags); - /* align */ - addr *= sizeof(u32); + seq_printf(s, "mode = %u\n", mode); - hw_write(ci, addr, ~0, data); return 0; } /** - * hw_intr_clear: disables interrupt & clears interrupt status (execute without - * interruption) - * @n: interrupt bit - * - * This function returns an error code + * ci_port_test_write: writes port test mode */ -static int hw_intr_clear(struct ci13xxx *ci, int n) +static ssize_t ci_port_test_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) { - if (n >= REG_BITS) - return -EINVAL; + struct seq_file *s = file->private_data; + struct ci_hdrc *ci = s->private; + unsigned long flags; + unsigned mode; + char buf[32]; + int ret; - hw_write(ci, OP_USBINTR, BIT(n), 0); - hw_write(ci, OP_USBSTS, BIT(n), BIT(n)); - return 0; -} + if (copy_from_user(buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; -/** - * hw_intr_force: enables interrupt & forces interrupt status (execute without - * interruption) - * @n: interrupt bit - * - * This function returns an error code - */ -static int hw_intr_force(struct ci13xxx *ci, int n) -{ - if (n >= REG_BITS) + if (sscanf(buf, "%u", &mode) != 1) return -EINVAL; - hw_write(ci, CAP_TESTMODE, TESTMODE_FORCE, TESTMODE_FORCE); - hw_write(ci, OP_USBINTR, BIT(n), BIT(n)); - hw_write(ci, OP_USBSTS, BIT(n), BIT(n)); - hw_write(ci, CAP_TESTMODE, TESTMODE_FORCE, 0); - return 0; + spin_lock_irqsave(&ci->lock, flags); + ret = hw_port_test_set(ci, mode); + spin_unlock_irqrestore(&ci->lock, flags); + + return ret ? ret : count; } -/** - * show_device: prints information about device capabilities and status - * - * Check "device.h" for details - */ -static ssize_t show_device(struct device *dev, struct device_attribute *attr, - char *buf) +static int ci_port_test_open(struct inode *inode, struct file *file) { - struct ci13xxx *ci = container_of(dev, struct ci13xxx, gadget.dev); - struct usb_gadget *gadget = &ci->gadget; - int n = 0; - - if (attr == NULL || buf == NULL) { - dev_err(ci->dev, "[%s] EINVAL\n", __func__); - return 0; - } - - n += scnprintf(buf + n, PAGE_SIZE - n, "speed = %d\n", - gadget->speed); - n += scnprintf(buf + n, PAGE_SIZE - n, "max_speed = %d\n", - gadget->max_speed); - n += scnprintf(buf + n, PAGE_SIZE - n, "is_otg = %d\n", - gadget->is_otg); - n += scnprintf(buf + n, PAGE_SIZE - n, "is_a_peripheral = %d\n", - gadget->is_a_peripheral); - n += scnprintf(buf + n, PAGE_SIZE - n, "b_hnp_enable = %d\n", - gadget->b_hnp_enable); - n += scnprintf(buf + n, PAGE_SIZE - n, "a_hnp_support = %d\n", - gadget->a_hnp_support); - n += scnprintf(buf + n, PAGE_SIZE - n, "a_alt_hnp_support = %d\n", - gadget->a_alt_hnp_support); - n += scnprintf(buf + n, PAGE_SIZE - n, "name = %s\n", - (gadget->name ? gadget->name : "")); - - return n; + return single_open(file, ci_port_test_show, inode->i_private); } -static DEVICE_ATTR(device, S_IRUSR, show_device, NULL); + +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, +}; /** - * show_driver: prints information about attached gadget (if any) - * - * Check "device.h" for details + * ci_qheads_show: DMA contents of all queue heads */ -static ssize_t show_driver(struct device *dev, struct device_attribute *attr, - char *buf) +static int ci_qheads_show(struct seq_file *s, void *data) { - struct ci13xxx *ci = container_of(dev, struct ci13xxx, gadget.dev); - struct usb_gadget_driver *driver = ci->driver; - int n = 0; + struct ci_hdrc *ci = s->private; + unsigned long flags; + unsigned i, j; - if (attr == NULL || buf == NULL) { - dev_err(dev, "[%s] EINVAL\n", __func__); + if (ci->role != CI_ROLE_GADGET) { + seq_printf(s, "not in gadget mode\n"); return 0; } - if (driver == NULL) - return scnprintf(buf, PAGE_SIZE, - "There is no gadget attached!\n"); - - n += scnprintf(buf + n, PAGE_SIZE - n, "function = %s\n", - (driver->function ? driver->function : "")); - n += scnprintf(buf + n, PAGE_SIZE - n, "max speed = %d\n", - driver->max_speed); + 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 n; + return 0; } -static DEVICE_ATTR(driver, S_IRUSR, show_driver, NULL); - -/* Maximum event message length */ -#define DBG_DATA_MSG 64UL - -/* Maximum event messages */ -#define DBG_DATA_MAX 128UL - -/* Event buffer descriptor */ -static struct { - char (buf[DBG_DATA_MAX])[DBG_DATA_MSG]; /* buffer */ - unsigned idx; /* index */ - unsigned tty; /* print to console? */ - rwlock_t lck; /* lock */ -} dbg_data = { - .idx = 0, - .tty = 0, - .lck = __RW_LOCK_UNLOCKED(lck) -}; -/** - * dbg_dec: decrements debug event index - * @idx: buffer index - */ -static void dbg_dec(unsigned *idx) +static int ci_qheads_open(struct inode *inode, struct file *file) { - *idx = (*idx - 1) & (DBG_DATA_MAX-1); + return single_open(file, ci_qheads_show, inode->i_private); } -/** - * dbg_inc: increments debug event index - * @idx: buffer index - */ -static void dbg_inc(unsigned *idx) -{ - *idx = (*idx + 1) & (DBG_DATA_MAX-1); -} +static const struct file_operations ci_qheads_fops = { + .open = ci_qheads_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; /** - * dbg_print: prints the common part of the event - * @addr: endpoint address - * @name: event name - * @status: status - * @extra: extra information + * ci_requests_show: DMA contents of all requests currently queued (all endpts) */ -static void dbg_print(u8 addr, const char *name, int status, const char *extra) +static int ci_requests_show(struct seq_file *s, void *data) { - struct timeval tval; - unsigned int stamp; + struct ci_hdrc *ci = s->private; unsigned long flags; + struct list_head *ptr = NULL; + struct ci_hw_req *req = NULL; + struct td_node *node, *tmpnode; + unsigned i, j, qsize = sizeof(struct ci_hw_td)/sizeof(u32); - write_lock_irqsave(&dbg_data.lck, flags); - - do_gettimeofday(&tval); - stamp = tval.tv_sec & 0xFFFF; /* 2^32 = 4294967296. Limit to 4096s */ - stamp = stamp * 1000000 + tval.tv_usec; - - scnprintf(dbg_data.buf[dbg_data.idx], DBG_DATA_MSG, - "%04X\t? %02X %-7.7s %4i ?\t%s\n", - stamp, addr, name, status, extra); - - dbg_inc(&dbg_data.idx); - - write_unlock_irqrestore(&dbg_data.lck, flags); - - if (dbg_data.tty != 0) - pr_notice("%04X\t? %02X %-7.7s %4i ?\t%s\n", - stamp, addr, name, status, extra); -} - -/** - * dbg_done: prints a DONE event - * @addr: endpoint address - * @td: transfer descriptor - * @status: status - */ -void dbg_done(u8 addr, const u32 token, int status) -{ - char msg[DBG_DATA_MSG]; + if (ci->role != CI_ROLE_GADGET) { + seq_printf(s, "not in gadget mode\n"); + return 0; + } - scnprintf(msg, sizeof(msg), "%d %02X", - (int)(token & TD_TOTAL_BYTES) >> ffs_nr(TD_TOTAL_BYTES), - (int)(token & TD_STATUS) >> ffs_nr(TD_STATUS)); - dbg_print(addr, "DONE", status, msg); -} + spin_lock_irqsave(&ci->lock, flags); + for (i = 0; i < ci->hw_ep_max; i++) + list_for_each(ptr, &ci->ci_hw_ep[i].qh.queue) { + req = list_entry(ptr, struct ci_hw_req, 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); -/** - * dbg_event: prints a generic event - * @addr: endpoint address - * @name: event name - * @status: status - */ -void dbg_event(u8 addr, const char *name, int status) -{ - if (name != NULL) - dbg_print(addr, name, status, ""); + return 0; } -/* - * dbg_queue: prints a QUEUE event - * @addr: endpoint address - * @req: USB request - * @status: status - */ -void dbg_queue(u8 addr, const struct usb_request *req, int status) +static int ci_requests_open(struct inode *inode, struct file *file) { - char msg[DBG_DATA_MSG]; - - if (req != NULL) { - scnprintf(msg, sizeof(msg), - "%d %d", !req->no_interrupt, req->length); - dbg_print(addr, "QUEUE", status, msg); - } + return single_open(file, ci_requests_show, inode->i_private); } -/** - * dbg_setup: prints a SETUP event - * @addr: endpoint address - * @req: setup request - */ -void dbg_setup(u8 addr, const struct usb_ctrlrequest *req) -{ - char msg[DBG_DATA_MSG]; - - if (req != NULL) { - scnprintf(msg, sizeof(msg), - "%02X %02X %04X %04X %d", req->bRequestType, - req->bRequest, le16_to_cpu(req->wValue), - le16_to_cpu(req->wIndex), le16_to_cpu(req->wLength)); - dbg_print(addr, "SETUP", 0, msg); - } -} +static const struct file_operations ci_requests_fops = { + .open = ci_requests_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; -/** - * show_events: displays the event buffer - * - * Check "device.h" for details - */ -static ssize_t show_events(struct device *dev, struct device_attribute *attr, - char *buf) +int ci_otg_show(struct seq_file *s, void *unused) { - unsigned long flags; - unsigned i, j, n = 0; + struct ci_hdrc *ci = s->private; + struct otg_fsm *fsm; - if (attr == NULL || buf == NULL) { - dev_err(dev->parent, "[%s] EINVAL\n", __func__); + if (!ci || !ci_otg_is_fsm_mode(ci)) return 0; - } - read_lock_irqsave(&dbg_data.lck, flags); + fsm = &ci->fsm; - i = dbg_data.idx; - for (dbg_dec(&i); i != dbg_data.idx; dbg_dec(&i)) { - n += strlen(dbg_data.buf[i]); - if (n >= PAGE_SIZE) { - n -= strlen(dbg_data.buf[i]); - break; - } - } - for (j = 0, dbg_inc(&i); j < n; dbg_inc(&i)) - j += scnprintf(buf + j, PAGE_SIZE - j, - "%s", dbg_data.buf[i]); + /* ------ State ----- */ + seq_printf(s, "OTG state: %s\n\n", + usb_otg_state_string(ci->transceiver->state)); - read_unlock_irqrestore(&dbg_data.lck, flags); + /* ------ State Machine Variables ----- */ + seq_printf(s, "a_bus_drop: %d\n", fsm->a_bus_drop); - return n; -} + seq_printf(s, "a_bus_req: %d\n", fsm->a_bus_req); -/** - * store_events: configure if events are going to be also printed to console - * - * Check "device.h" for details - */ -static ssize_t store_events(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - unsigned tty; + seq_printf(s, "a_srp_det: %d\n", fsm->a_srp_det); - if (attr == NULL || buf == NULL) { - dev_err(dev, "[%s] EINVAL\n", __func__); - goto done; - } + seq_printf(s, "a_vbus_vld: %d\n", fsm->a_vbus_vld); - if (sscanf(buf, "%u", &tty) != 1 || tty > 1) { - dev_err(dev, "<1|0>: enable|disable console log\n"); - goto done; - } + seq_printf(s, "b_conn: %d\n", fsm->b_conn); - dbg_data.tty = tty; - dev_info(dev, "tty = %u", dbg_data.tty); + seq_printf(s, "adp_change: %d\n", fsm->adp_change); - done: - return count; -} -static DEVICE_ATTR(events, S_IRUSR | S_IWUSR, show_events, store_events); + seq_printf(s, "power_up: %d\n", fsm->power_up); -/** - * show_inters: interrupt status, enable status and historic - * - * Check "device.h" for details - */ -static ssize_t show_inters(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct ci13xxx *ci = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long flags; - u32 intr; - unsigned i, j, n = 0; + seq_printf(s, "a_bus_resume: %d\n", fsm->a_bus_resume); - if (attr == NULL || buf == NULL) { - dev_err(ci->dev, "[%s] EINVAL\n", __func__); - return 0; - } + seq_printf(s, "a_bus_suspend: %d\n", fsm->a_bus_suspend); - spin_lock_irqsave(&ci->lock, flags); + seq_printf(s, "a_conn: %d\n", fsm->a_conn); - /*n += scnprintf(buf + n, PAGE_SIZE - n, - "status = %08x\n", hw_read_intr_status(ci)); - n += scnprintf(buf + n, PAGE_SIZE - n, - "enable = %08x\n", hw_read_intr_enable(ci));*/ - - n += scnprintf(buf + n, PAGE_SIZE - n, "*test = %d\n", - isr_statistics.test); - n += scnprintf(buf + n, PAGE_SIZE - n, "? ui = %d\n", - isr_statistics.ui); - n += scnprintf(buf + n, PAGE_SIZE - n, "? uei = %d\n", - isr_statistics.uei); - n += scnprintf(buf + n, PAGE_SIZE - n, "? pci = %d\n", - isr_statistics.pci); - n += scnprintf(buf + n, PAGE_SIZE - n, "? uri = %d\n", - isr_statistics.uri); - n += scnprintf(buf + n, PAGE_SIZE - n, "? sli = %d\n", - isr_statistics.sli); - n += scnprintf(buf + n, PAGE_SIZE - n, "*none = %d\n", - isr_statistics.none); - n += scnprintf(buf + n, PAGE_SIZE - n, "*hndl = %d\n", - isr_statistics.hndl.cnt); - - for (i = isr_statistics.hndl.idx, j = 0; j <= ISR_MASK; j++, i++) { - i &= ISR_MASK; - intr = isr_statistics.hndl.buf[i]; - - if (USBi_UI & intr) - n += scnprintf(buf + n, PAGE_SIZE - n, "ui "); - intr &= ~USBi_UI; - if (USBi_UEI & intr) - n += scnprintf(buf + n, PAGE_SIZE - n, "uei "); - intr &= ~USBi_UEI; - if (USBi_PCI & intr) - n += scnprintf(buf + n, PAGE_SIZE - n, "pci "); - intr &= ~USBi_PCI; - if (USBi_URI & intr) - n += scnprintf(buf + n, PAGE_SIZE - n, "uri "); - intr &= ~USBi_URI; - if (USBi_SLI & intr) - n += scnprintf(buf + n, PAGE_SIZE - n, "sli "); - intr &= ~USBi_SLI; - if (intr) - n += scnprintf(buf + n, PAGE_SIZE - n, "??? "); - if (isr_statistics.hndl.buf[i]) - n += scnprintf(buf + n, PAGE_SIZE - n, "\n"); - } + seq_printf(s, "b_bus_req: %d\n", fsm->b_bus_req); - spin_unlock_irqrestore(&ci->lock, flags); + seq_printf(s, "b_bus_suspend: %d\n", fsm->b_bus_suspend); - return n; -} + seq_printf(s, "b_se0_srp: %d\n", fsm->b_se0_srp); -/** - * store_inters: enable & force or disable an individual interrutps - * (to be used for test purposes only) - * - * Check "device.h" for details - */ -static ssize_t store_inters(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct ci13xxx *ci = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long flags; - unsigned en, bit; + seq_printf(s, "b_ssend_srp: %d\n", fsm->b_ssend_srp); - if (attr == NULL || buf == NULL) { - dev_err(ci->dev, "EINVAL\n"); - goto done; - } + seq_printf(s, "b_sess_vld: %d\n", fsm->b_sess_vld); - if (sscanf(buf, "%u %u", &en, &bit) != 2 || en > 1) { - dev_err(ci->dev, "<1|0> <bit>: enable|disable interrupt\n"); - goto done; - } + seq_printf(s, "b_srp_done: %d\n", fsm->b_srp_done); - spin_lock_irqsave(&ci->lock, flags); - if (en) { - if (hw_intr_force(ci, bit)) - dev_err(dev, "invalid bit number\n"); - else - isr_statistics.test++; - } else { - if (hw_intr_clear(ci, bit)) - dev_err(dev, "invalid bit number\n"); - } - spin_unlock_irqrestore(&ci->lock, flags); + seq_printf(s, "drv_vbus: %d\n", fsm->drv_vbus); - done: - return count; -} -static DEVICE_ATTR(inters, S_IRUSR | S_IWUSR, show_inters, store_inters); + seq_printf(s, "loc_conn: %d\n", fsm->loc_conn); -/** - * show_port_test: reads port test mode - * - * Check "device.h" for details - */ -static ssize_t show_port_test(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct ci13xxx *ci = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long flags; - unsigned mode; + seq_printf(s, "loc_sof: %d\n", fsm->loc_sof); - if (attr == NULL || buf == NULL) { - dev_err(ci->dev, "EINVAL\n"); - return 0; - } + seq_printf(s, "adp_prb: %d\n", fsm->adp_prb); - spin_lock_irqsave(&ci->lock, flags); - mode = hw_port_test_get(ci); - spin_unlock_irqrestore(&ci->lock, flags); + seq_printf(s, "id: %d\n", fsm->id); + + seq_printf(s, "protocol: %d\n", fsm->protocol); - return scnprintf(buf, PAGE_SIZE, "mode = %u\n", mode); + return 0; } -/** - * store_port_test: writes port test mode - * - * Check "device.h" for details - */ -static ssize_t store_port_test(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static int ci_otg_open(struct inode *inode, struct file *file) { - struct ci13xxx *ci = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long flags; - unsigned mode; + return single_open(file, ci_otg_show, inode->i_private); +} - if (attr == NULL || buf == NULL) { - dev_err(ci->dev, "[%s] EINVAL\n", __func__); - goto done; - } +static const struct file_operations ci_otg_fops = { + .open = ci_otg_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; - if (sscanf(buf, "%u", &mode) != 1) { - dev_err(ci->dev, "<mode>: set port test mode"); - goto done; - } +static int ci_role_show(struct seq_file *s, void *data) +{ + struct ci_hdrc *ci = s->private; - spin_lock_irqsave(&ci->lock, flags); - if (hw_port_test_set(ci, mode)) - dev_err(ci->dev, "invalid mode\n"); - spin_unlock_irqrestore(&ci->lock, flags); + seq_printf(s, "%s\n", ci_role(ci)->name); - done: - return count; + return 0; } -static DEVICE_ATTR(port_test, S_IRUSR | S_IWUSR, - show_port_test, store_port_test); -/** - * show_qheads: DMA contents of all queue heads - * - * Check "device.h" for details - */ -static ssize_t show_qheads(struct device *dev, struct device_attribute *attr, - char *buf) +static ssize_t ci_role_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) { - struct ci13xxx *ci = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long flags; - unsigned i, j, n = 0; + struct seq_file *s = file->private_data; + struct ci_hdrc *ci = s->private; + enum ci_role role; + char buf[8]; + int ret; + + if (copy_from_user(buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + for (role = CI_ROLE_HOST; role < CI_ROLE_END; role++) + if (ci->roles[role] && + !strncmp(buf, ci->roles[role]->name, + strlen(ci->roles[role]->name))) + break; - if (attr == NULL || buf == NULL) { - dev_err(ci->dev, "[%s] EINVAL\n", __func__); - return 0; - } + if (role == CI_ROLE_END || role == ci->role) + return -EINVAL; - spin_lock_irqsave(&ci->lock, flags); - for (i = 0; i < ci->hw_ep_max/2; i++) { - struct ci13xxx_ep *mEpRx = &ci->ci13xxx_ep[i]; - struct ci13xxx_ep *mEpTx = - &ci->ci13xxx_ep[i + ci->hw_ep_max/2]; - n += scnprintf(buf + n, PAGE_SIZE - n, - "EP=%02i: RX=%08X TX=%08X\n", - i, (u32)mEpRx->qh.dma, (u32)mEpTx->qh.dma); - for (j = 0; j < (sizeof(struct ci13xxx_qh)/sizeof(u32)); j++) { - n += scnprintf(buf + n, PAGE_SIZE - n, - " %04X: %08X %08X\n", j, - *((u32 *)mEpRx->qh.ptr + j), - *((u32 *)mEpTx->qh.ptr + j)); - } - } - spin_unlock_irqrestore(&ci->lock, flags); + ci_role_stop(ci); + ret = ci_role_start(ci, role); - return n; + return ret ? ret : count; } -static DEVICE_ATTR(qheads, S_IRUSR, show_qheads, NULL); -/** - * show_registers: dumps all registers - * - * Check "device.h" for details - */ -#define DUMP_ENTRIES 512 -static ssize_t show_registers(struct device *dev, - struct device_attribute *attr, char *buf) +static int ci_role_open(struct inode *inode, struct file *file) { - struct ci13xxx *ci = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long flags; - u32 *dump; - unsigned i, k, n = 0; + return single_open(file, ci_role_show, inode->i_private); +} - if (attr == NULL || buf == NULL) { - dev_err(ci->dev, "[%s] EINVAL\n", __func__); - return 0; - } +static const struct file_operations ci_role_fops = { + .open = ci_role_open, + .write = ci_role_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; - dump = kmalloc(sizeof(u32) * DUMP_ENTRIES, GFP_KERNEL); - if (!dump) { - dev_err(ci->dev, "%s: out of memory\n", __func__); +int ci_registers_show(struct seq_file *s, void *unused) +{ + struct ci_hdrc *ci = s->private; + u32 tmp_reg; + + if (!ci) return 0; - } - spin_lock_irqsave(&ci->lock, flags); - k = hw_register_read(ci, dump, DUMP_ENTRIES); - spin_unlock_irqrestore(&ci->lock, flags); + /* ------ Registers ----- */ + tmp_reg = hw_read_intr_enable(ci); + seq_printf(s, "USBINTR reg: %08x\n", tmp_reg); - for (i = 0; i < k; i++) { - n += scnprintf(buf + n, PAGE_SIZE - n, - "reg[0x%04X] = 0x%08X\n", - i * (unsigned)sizeof(u32), dump[i]); - } - kfree(dump); + tmp_reg = hw_read_intr_status(ci); + seq_printf(s, "USBSTS reg: %08x\n", tmp_reg); - return n; -} + tmp_reg = hw_read(ci, OP_USBMODE, ~0); + seq_printf(s, "USBMODE reg: %08x\n", tmp_reg); -/** - * store_registers: writes value to register address - * - * Check "device.h" for details - */ -static ssize_t store_registers(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct ci13xxx *ci = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long addr, data, flags; + tmp_reg = hw_read(ci, OP_USBCMD, ~0); + seq_printf(s, "USBCMD reg: %08x\n", tmp_reg); - if (attr == NULL || buf == NULL) { - dev_err(ci->dev, "[%s] EINVAL\n", __func__); - goto done; - } + tmp_reg = hw_read(ci, OP_PORTSC, ~0); + seq_printf(s, "PORTSC reg: %08x\n", tmp_reg); - if (sscanf(buf, "%li %li", &addr, &data) != 2) { - dev_err(ci->dev, - "<addr> <data>: write data to register address\n"); - goto done; + if (ci->is_otg) { + tmp_reg = hw_read_otgsc(ci, ~0); + seq_printf(s, "OTGSC reg: %08x\n", tmp_reg); } - spin_lock_irqsave(&ci->lock, flags); - if (hw_register_write(ci, addr, data)) - dev_err(ci->dev, "invalid address range\n"); - spin_unlock_irqrestore(&ci->lock, flags); - - done: - return count; + return 0; } -static DEVICE_ATTR(registers, S_IRUSR | S_IWUSR, - show_registers, store_registers); -/** - * show_requests: DMA contents of all requests currently queued (all endpts) - * - * Check "device.h" for details - */ -static ssize_t show_requests(struct device *dev, struct device_attribute *attr, - char *buf) +static int ci_registers_open(struct inode *inode, struct file *file) { - struct ci13xxx *ci = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long flags; - struct list_head *ptr = NULL; - struct ci13xxx_req *req = NULL; - unsigned i, j, n = 0, qSize = sizeof(struct ci13xxx_td)/sizeof(u32); - - if (attr == NULL || buf == NULL) { - dev_err(ci->dev, "[%s] EINVAL\n", __func__); - return 0; - } - - spin_lock_irqsave(&ci->lock, flags); - for (i = 0; i < ci->hw_ep_max; i++) - list_for_each(ptr, &ci->ci13xxx_ep[i].qh.queue) - { - req = list_entry(ptr, struct ci13xxx_req, queue); - - n += scnprintf(buf + n, PAGE_SIZE - n, - "EP=%02i: TD=%08X %s\n", - i % ci->hw_ep_max/2, (u32)req->dma, - ((i < ci->hw_ep_max/2) ? "RX" : "TX")); - - for (j = 0; j < qSize; j++) - n += scnprintf(buf + n, PAGE_SIZE - n, - " %04X: %08X\n", j, - *((u32 *)req->ptr + j)); - } - spin_unlock_irqrestore(&ci->lock, flags); - - return n; + return single_open(file, ci_registers_show, inode->i_private); } -static DEVICE_ATTR(requests, S_IRUSR, show_requests, NULL); + +static const struct file_operations ci_registers_fops = { + .open = ci_registers_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; /** * dbg_create_files: initializes the attribute interface - * @dev: device + * @ci: device * * This function returns an error code */ -int dbg_create_files(struct device *dev) +int dbg_create_files(struct ci_hdrc *ci) { - int retval = 0; + struct dentry *dent; + + ci->debugfs = debugfs_create_dir(dev_name(ci->dev), NULL); + if (!ci->debugfs) + return -ENOMEM; + + dent = debugfs_create_file("device", S_IRUGO, ci->debugfs, ci, + &ci_device_fops); + if (!dent) + goto err; + + dent = debugfs_create_file("port_test", S_IRUGO | S_IWUSR, ci->debugfs, + ci, &ci_port_test_fops); + if (!dent) + goto err; + + dent = debugfs_create_file("qheads", S_IRUGO, ci->debugfs, ci, + &ci_qheads_fops); + if (!dent) + goto err; + + dent = debugfs_create_file("requests", S_IRUGO, ci->debugfs, ci, + &ci_requests_fops); + if (!dent) + goto err; + + if (ci_otg_is_fsm_mode(ci)) { + dent = debugfs_create_file("otg", S_IRUGO, ci->debugfs, ci, + &ci_otg_fops); + if (!dent) + goto err; + } - if (dev == NULL) - return -EINVAL; - retval = device_create_file(dev, &dev_attr_device); - if (retval) - goto done; - retval = device_create_file(dev, &dev_attr_driver); - if (retval) - goto rm_device; - retval = device_create_file(dev, &dev_attr_events); - if (retval) - goto rm_driver; - retval = device_create_file(dev, &dev_attr_inters); - if (retval) - goto rm_events; - retval = device_create_file(dev, &dev_attr_port_test); - if (retval) - goto rm_inters; - retval = device_create_file(dev, &dev_attr_qheads); - if (retval) - goto rm_port_test; - retval = device_create_file(dev, &dev_attr_registers); - if (retval) - goto rm_qheads; - retval = device_create_file(dev, &dev_attr_requests); - if (retval) - goto rm_registers; - return 0; + dent = debugfs_create_file("role", S_IRUGO | S_IWUSR, ci->debugfs, ci, + &ci_role_fops); + if (!dent) + goto err; - rm_registers: - device_remove_file(dev, &dev_attr_registers); - rm_qheads: - device_remove_file(dev, &dev_attr_qheads); - rm_port_test: - device_remove_file(dev, &dev_attr_port_test); - rm_inters: - device_remove_file(dev, &dev_attr_inters); - rm_events: - device_remove_file(dev, &dev_attr_events); - rm_driver: - device_remove_file(dev, &dev_attr_driver); - rm_device: - device_remove_file(dev, &dev_attr_device); - done: - return retval; + dent = debugfs_create_file("registers", S_IRUGO, ci->debugfs, ci, + &ci_registers_fops); + + if (dent) + return 0; +err: + debugfs_remove_recursive(ci->debugfs); + return -ENOMEM; } /** * dbg_remove_files: destroys the attribute interface - * @dev: device - * - * This function returns an error code + * @ci: device */ -int dbg_remove_files(struct device *dev) +void dbg_remove_files(struct ci_hdrc *ci) { - if (dev == NULL) - return -EINVAL; - device_remove_file(dev, &dev_attr_requests); - device_remove_file(dev, &dev_attr_registers); - device_remove_file(dev, &dev_attr_qheads); - device_remove_file(dev, &dev_attr_port_test); - device_remove_file(dev, &dev_attr_inters); - device_remove_file(dev, &dev_attr_events); - device_remove_file(dev, &dev_attr_driver); - device_remove_file(dev, &dev_attr_device); - return 0; + debugfs_remove_recursive(ci->debugfs); } diff --git a/drivers/usb/chipidea/debug.h b/drivers/usb/chipidea/debug.h index 80d96865775..e16478c4a94 100644 --- a/drivers/usb/chipidea/debug.h +++ b/drivers/usb/chipidea/debug.h @@ -14,42 +14,16 @@ #define __DRIVERS_USB_CHIPIDEA_DEBUG_H #ifdef CONFIG_USB_CHIPIDEA_DEBUG -void dbg_interrupt(u32 intmask); -void dbg_done(u8 addr, const u32 token, int status); -void dbg_event(u8 addr, const char *name, int status); -void dbg_queue(u8 addr, const struct usb_request *req, int status); -void dbg_setup(u8 addr, const struct usb_ctrlrequest *req); -int dbg_create_files(struct device *dev); -int dbg_remove_files(struct device *dev); +int dbg_create_files(struct ci_hdrc *ci); +void dbg_remove_files(struct ci_hdrc *ci); #else -static inline void dbg_interrupt(u32 intmask) -{ -} - -static inline void dbg_done(u8 addr, const u32 token, int status) -{ -} - -static inline void dbg_event(u8 addr, const char *name, int status) -{ -} - -static inline void dbg_queue(u8 addr, const struct usb_request *req, int status) -{ -} - -static inline void dbg_setup(u8 addr, const struct usb_ctrlrequest *req) -{ -} - -static inline int dbg_create_files(struct device *dev) +static inline int dbg_create_files(struct ci_hdrc *ci) { return 0; } -static inline int dbg_remove_files(struct device *dev) +static inline void dbg_remove_files(struct ci_hdrc *ci) { - return 0; } #endif diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 8e9d31277c4..a93d950e946 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -24,6 +24,7 @@ #include <linux/usb.h> #include <linux/usb/hcd.h> #include <linux/usb/chipidea.h> +#include <linux/regulator/consumer.h> #include "../host/ehci.h" @@ -33,12 +34,12 @@ static struct hc_driver __read_mostly ci_ehci_hc_driver; -static irqreturn_t host_irq(struct ci13xxx *ci) +static irqreturn_t host_irq(struct ci_hdrc *ci) { return usb_hcd_irq(ci->irq, ci->hcd); } -static int host_start(struct ci13xxx *ci) +static int host_start(struct ci_hdrc *ci) { struct usb_hcd *hcd; struct ehci_hcd *ehci; @@ -63,28 +64,71 @@ static int host_start(struct ci13xxx *ci) 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; + + /* + * vbus is always on if host is not in OTG FSM mode, + * otherwise should be controlled by OTG FSM + */ + if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci)) { + 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; + } + } ret = usb_add_hcd(hcd, 0, 0); - if (ret) - usb_put_hcd(hcd); - else + if (ret) { + goto disable_reg; + } else { + struct usb_otg *otg = ci->transceiver->otg; + ci->hcd = hcd; + if (otg) { + otg->host = &hcd->self; + hcd->self.otg_port = 1; + } + } - if (ci->platdata->flags & CI13XXX_DISABLE_STREAMING) + if (ci->platdata->flags & CI_HDRC_DISABLE_STREAMING) hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS); return ret; + +disable_reg: + if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci)) + regulator_disable(ci->platdata->reg_vbus); + +put_hcd: + usb_put_hcd(hcd); + + return ret; } -static void host_stop(struct ci13xxx *ci) +static void host_stop(struct ci_hdrc *ci) { struct usb_hcd *hcd = ci->hcd; - usb_remove_hcd(hcd); - usb_put_hcd(hcd); + if (hcd) { + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci)) + regulator_disable(ci->platdata->reg_vbus); + } +} + + +void ci_hdrc_host_destroy(struct ci_hdrc *ci) +{ + if (ci->role == CI_ROLE_HOST && ci->hcd) + host_stop(ci); } -int ci_hdrc_host_init(struct ci13xxx *ci) +int ci_hdrc_host_init(struct ci_hdrc *ci) { struct ci_role_driver *rdrv; diff --git a/drivers/usb/chipidea/host.h b/drivers/usb/chipidea/host.h index 761fb1fd6d9..5707bf379bf 100644 --- a/drivers/usb/chipidea/host.h +++ b/drivers/usb/chipidea/host.h @@ -3,15 +3,21 @@ #ifdef CONFIG_USB_CHIPIDEA_HOST -int ci_hdrc_host_init(struct ci13xxx *ci); +int ci_hdrc_host_init(struct ci_hdrc *ci); +void ci_hdrc_host_destroy(struct ci_hdrc *ci); #else -static inline int ci_hdrc_host_init(struct ci13xxx *ci) +static inline int ci_hdrc_host_init(struct ci_hdrc *ci) { return -ENXIO; } +static inline void ci_hdrc_host_destroy(struct ci_hdrc *ci) +{ + +} + #endif #endif /* __DRIVERS_USB_CHIPIDEA_HOST_H */ diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c new file mode 100644 index 00000000000..a048b08b9d4 --- /dev/null +++ b/drivers/usb/chipidea/otg.c @@ -0,0 +1,146 @@ +/* + * otg.c - ChipIdea USB IP core OTG driver + * + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * Author: Peter Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * 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. + * @mask: bitfield mask + */ +u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) +{ + return hw_read(ci, OP_OTGSC, mask); +} + +/** + * hw_write_otgsc updates target bits of OTGSC register. + * @mask: bitfield mask + * @data: to be written + */ +void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data) +{ + 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->is_otg) + return; + + if (hw_read_otgsc(ci, OTGSC_BSV)) + usb_gadget_vbus_connect(&ci->gadget); + else + usb_gadget_vbus_disconnect(&ci->gadget); +} + +#define CI_VBUS_STABLE_TIMEOUT_MS 5000 +static void ci_handle_id_switch(struct ci_hdrc *ci) +{ + enum ci_role 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); + + ci_role_stop(ci); + /* wait vbus lower than OTGSC_BSV */ + hw_wait_reg(ci, OP_OTGSC, OTGSC_BSV, 0, + CI_VBUS_STABLE_TIMEOUT_MS); + ci_role_start(ci, role); + } +} +/** + * 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; + } + + if (ci->id_event) { + ci->id_event = false; + ci_handle_id_switch(ci); + } else if (ci->b_sess_valid_event) { + ci->b_sess_valid_event = false; + ci_handle_vbus_change(ci); + } else + dev_err(ci->dev, "unexpected event occurs at %s\n", __func__); + + 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_singlethread_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) { + flush_workqueue(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); +} diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h new file mode 100644 index 00000000000..9ecb598e48f --- /dev/null +++ b/drivers/usb/chipidea/otg.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2013-2014 Freescale Semiconductor, Inc. + * + * Author: Peter Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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); +static inline void ci_otg_queue_work(struct ci_hdrc *ci) +{ + disable_irq_nosync(ci->irq); + queue_work(ci->wq, &ci->work); +} + +#endif /* __DRIVERS_USB_CHIPIDEA_OTG_H */ diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c new file mode 100644 index 00000000000..caaabc58021 --- /dev/null +++ b/drivers/usb/chipidea/otg_fsm.c @@ -0,0 +1,842 @@ +/* + * otg_fsm.c - ChipIdea USB IP core OTG FSM driver + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Jun Li + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * 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" + +static struct ci_otg_fsm_timer *otg_timer_initializer +(struct ci_hdrc *ci, void (*function)(void *, unsigned long), + unsigned long expires, unsigned long data) +{ + struct ci_otg_fsm_timer *timer; + + timer = devm_kzalloc(ci->dev, sizeof(struct ci_otg_fsm_timer), + GFP_KERNEL); + if (!timer) + return NULL; + timer->function = function; + timer->expires = expires; + timer->data = data; + return timer; +} + +/* Add for otg: interact with user space app */ +static ssize_t +get_a_bus_req(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 +set_a_bus_req(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; + } + + ci_otg_queue_work(ci); + mutex_unlock(&ci->fsm.lock); + + return count; +} +static DEVICE_ATTR(a_bus_req, S_IRUGO | S_IWUSR, get_a_bus_req, set_a_bus_req); + +static ssize_t +get_a_bus_drop(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 +set_a_bus_drop(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(a_bus_drop, S_IRUGO | S_IWUSR, get_a_bus_drop, + set_a_bus_drop); + +static ssize_t +get_b_bus_req(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 +set_b_bus_req(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; + + ci_otg_queue_work(ci); + mutex_unlock(&ci->fsm.lock); + + return count; +} +static DEVICE_ATTR(b_bus_req, S_IRUGO | S_IWUSR, get_b_bus_req, set_b_bus_req); + +static ssize_t +set_a_clr_err(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(a_clr_err, S_IWUSR, NULL, set_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 struct attribute_group inputs_attr_group = { + .name = "inputs", + .attrs = inputs_attrs, +}; + +/* + * Add timer to active timer list + */ +static void ci_otg_add_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t) +{ + struct ci_otg_fsm_timer *tmp_timer; + struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t]; + struct list_head *active_timers = &ci->fsm_timer->active_timers; + + if (t >= NUM_CI_OTG_FSM_TIMERS) + return; + + /* + * Check if the timer is already in the active list, + * if so update timer count + */ + list_for_each_entry(tmp_timer, active_timers, list) + if (tmp_timer == timer) { + timer->count = timer->expires; + return; + } + + timer->count = timer->expires; + list_add_tail(&timer->list, active_timers); + + /* Enable 1ms irq */ + if (!(hw_read_otgsc(ci, OTGSC_1MSIE))) + hw_write_otgsc(ci, OTGSC_1MSIE, OTGSC_1MSIE); +} + +/* + * Remove timer from active timer list + */ +static void ci_otg_del_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t) +{ + struct ci_otg_fsm_timer *tmp_timer, *del_tmp; + struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t]; + struct list_head *active_timers = &ci->fsm_timer->active_timers; + + if (t >= NUM_CI_OTG_FSM_TIMERS) + return; + + list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list) + if (tmp_timer == timer) + list_del(&timer->list); + + /* Disable 1ms irq if there is no any active timer */ + if (list_empty(active_timers)) + hw_write_otgsc(ci, OTGSC_1MSIE, 0); +} + +/* + * Reduce timer count by 1, and find timeout conditions. + * Called by otg 1ms timer interrupt + */ +static inline int ci_otg_tick_timer(struct ci_hdrc *ci) +{ + struct ci_otg_fsm_timer *tmp_timer, *del_tmp; + struct list_head *active_timers = &ci->fsm_timer->active_timers; + int expired = 0; + + list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list) { + tmp_timer->count--; + /* check if timer expires */ + if (!tmp_timer->count) { + list_del(&tmp_timer->list); + tmp_timer->function(ci, tmp_timer->data); + expired = 1; + } + } + + /* disable 1ms irq if there is no any timer active */ + if ((expired == 1) && list_empty(active_timers)) + hw_write_otgsc(ci, OTGSC_1MSIE, 0); + + return expired; +} + +/* The timeout callback function to set time out bit */ +static void set_tmout(void *ptr, unsigned long indicator) +{ + *(int *)indicator = 1; +} + +static void set_tmout_and_fsm(void *ptr, unsigned long indicator) +{ + struct ci_hdrc *ci = (struct ci_hdrc *)ptr; + + set_tmout(ci, indicator); + + ci_otg_queue_work(ci); +} + +static void a_wait_vfall_tmout_func(void *ptr, unsigned long indicator) +{ + struct ci_hdrc *ci = (struct ci_hdrc *)ptr; + + set_tmout(ci, indicator); + /* Disable port power */ + hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, 0); + /* Clear exsiting DP irq */ + hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); + /* Enable data pulse irq */ + hw_write_otgsc(ci, OTGSC_DPIE, OTGSC_DPIE); + ci_otg_queue_work(ci); +} + +static void b_ase0_brst_tmout_func(void *ptr, unsigned long indicator) +{ + struct ci_hdrc *ci = (struct ci_hdrc *)ptr; + + set_tmout(ci, indicator); + if (!hw_read_otgsc(ci, OTGSC_BSV)) + ci->fsm.b_sess_vld = 0; + + ci_otg_queue_work(ci); +} + +static void b_ssend_srp_tmout_func(void *ptr, unsigned long indicator) +{ + struct ci_hdrc *ci = (struct ci_hdrc *)ptr; + + set_tmout(ci, indicator); + + /* only vbus fall below B_sess_vld in b_idle state */ + if (ci->transceiver->state == OTG_STATE_B_IDLE) + ci_otg_queue_work(ci); +} + +static void b_sess_vld_tmout_func(void *ptr, unsigned long indicator) +{ + struct ci_hdrc *ci = (struct ci_hdrc *)ptr; + + /* Check if A detached */ + if (!(hw_read_otgsc(ci, OTGSC_BSV))) { + ci->fsm.b_sess_vld = 0; + ci_otg_add_timer(ci, B_SSEND_SRP); + ci_otg_queue_work(ci); + } +} + +static void b_data_pulse_end(void *ptr, unsigned long indicator) +{ + struct ci_hdrc *ci = (struct ci_hdrc *)ptr; + + 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); + + ci_otg_queue_work(ci); +} + +/* Initialize timers */ +static int ci_otg_init_timers(struct ci_hdrc *ci) +{ + struct otg_fsm *fsm = &ci->fsm; + + /* FSM used timers */ + ci->fsm_timer->timer_list[A_WAIT_VRISE] = + otg_timer_initializer(ci, &set_tmout_and_fsm, TA_WAIT_VRISE, + (unsigned long)&fsm->a_wait_vrise_tmout); + if (ci->fsm_timer->timer_list[A_WAIT_VRISE] == NULL) + return -ENOMEM; + + ci->fsm_timer->timer_list[A_WAIT_VFALL] = + otg_timer_initializer(ci, &a_wait_vfall_tmout_func, + TA_WAIT_VFALL, (unsigned long)&fsm->a_wait_vfall_tmout); + if (ci->fsm_timer->timer_list[A_WAIT_VFALL] == NULL) + return -ENOMEM; + + ci->fsm_timer->timer_list[A_WAIT_BCON] = + otg_timer_initializer(ci, &set_tmout_and_fsm, TA_WAIT_BCON, + (unsigned long)&fsm->a_wait_bcon_tmout); + if (ci->fsm_timer->timer_list[A_WAIT_BCON] == NULL) + return -ENOMEM; + + ci->fsm_timer->timer_list[A_AIDL_BDIS] = + otg_timer_initializer(ci, &set_tmout_and_fsm, TA_AIDL_BDIS, + (unsigned long)&fsm->a_aidl_bdis_tmout); + if (ci->fsm_timer->timer_list[A_AIDL_BDIS] == NULL) + return -ENOMEM; + + ci->fsm_timer->timer_list[A_BIDL_ADIS] = + otg_timer_initializer(ci, &set_tmout_and_fsm, TA_BIDL_ADIS, + (unsigned long)&fsm->a_bidl_adis_tmout); + if (ci->fsm_timer->timer_list[A_BIDL_ADIS] == NULL) + return -ENOMEM; + + ci->fsm_timer->timer_list[B_ASE0_BRST] = + otg_timer_initializer(ci, &b_ase0_brst_tmout_func, TB_ASE0_BRST, + (unsigned long)&fsm->b_ase0_brst_tmout); + if (ci->fsm_timer->timer_list[B_ASE0_BRST] == NULL) + return -ENOMEM; + + ci->fsm_timer->timer_list[B_SE0_SRP] = + otg_timer_initializer(ci, &set_tmout_and_fsm, TB_SE0_SRP, + (unsigned long)&fsm->b_se0_srp); + if (ci->fsm_timer->timer_list[B_SE0_SRP] == NULL) + return -ENOMEM; + + ci->fsm_timer->timer_list[B_SSEND_SRP] = + otg_timer_initializer(ci, &b_ssend_srp_tmout_func, TB_SSEND_SRP, + (unsigned long)&fsm->b_ssend_srp); + if (ci->fsm_timer->timer_list[B_SSEND_SRP] == NULL) + return -ENOMEM; + + ci->fsm_timer->timer_list[B_SRP_FAIL] = + otg_timer_initializer(ci, &set_tmout, TB_SRP_FAIL, + (unsigned long)&fsm->b_srp_done); + if (ci->fsm_timer->timer_list[B_SRP_FAIL] == NULL) + return -ENOMEM; + + ci->fsm_timer->timer_list[B_DATA_PLS] = + otg_timer_initializer(ci, &b_data_pulse_end, TB_DATA_PLS, 0); + if (ci->fsm_timer->timer_list[B_DATA_PLS] == NULL) + return -ENOMEM; + + ci->fsm_timer->timer_list[B_SESS_VLD] = otg_timer_initializer(ci, + &b_sess_vld_tmout_func, TB_SESS_VLD, 0); + if (ci->fsm_timer->timer_list[B_SESS_VLD] == NULL) + return -ENOMEM; + + 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 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; + } + } + /* 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); + + 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. + * This is controlled through suspend/resume the port. + * In host mode, controller will automatically send SOF. + * Suspend will block the data on the port. + */ +static void ci_otg_loc_sof(struct otg_fsm *fsm, int on) +{ + struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); + + if (on) + hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_FPR, + PORTSC_FPR); + else + hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_SUSP, + PORTSC_SUSP); +} + +/* + * 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); + + 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); + + mutex_unlock(&fsm->lock); + if (on) { + ci_role_stop(ci); + ci_role_start(ci, CI_ROLE_HOST); + } else { + ci_role_stop(ci); + hw_device_reset(ci, USBMODE_CM_DC); + ci_role_start(ci, CI_ROLE_GADGET); + } + mutex_lock(&fsm->lock); + 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); + + mutex_unlock(&fsm->lock); + if (on) + usb_gadget_vbus_connect(&ci->gadget); + else + usb_gadget_vbus_disconnect(&ci->gadget); + mutex_lock(&fsm->lock); + + 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->transceiver->state < OTG_STATE_A_IDLE) + return 0; + + if (otg_statemachine(&ci->fsm)) { + if (ci->transceiver->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); + if (ci->id_event) + ci->id_event = false; + } else if (ci->transceiver->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); + } + } + } + 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->transceiver->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) { + fsm->a_bus_suspend = 1; + ci_otg_queue_work(ci); + } else if (intr_sts & USBi_PCI) { + 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); + ci_otg_add_timer(ci, B_SESS_VLD); + } + 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 contine, 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_1MSIS) { + hw_write_otgsc(ci, OTGSC_1MSIS, OTGSC_1MSIS); + retval = ci_otg_tick_timer(ci); + return IRQ_HANDLED; + } else 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; + struct usb_otg *otg; + + otg = devm_kzalloc(ci->dev, + sizeof(struct usb_otg), GFP_KERNEL); + if (!otg) { + dev_err(ci->dev, + "Failed to allocate usb_otg structure for ci hdrc otg!\n"); + return -ENOMEM; + } + + otg->phy = ci->transceiver; + otg->gadget = &ci->gadget; + ci->fsm.otg = otg; + ci->transceiver->otg = ci->fsm.otg; + ci->fsm.power_up = 1; + ci->fsm.id = hw_read_otgsc(ci, OTGSC_ID) ? 1 : 0; + ci->transceiver->state = OTG_STATE_UNDEFINED; + ci->fsm.ops = &ci_otg_ops; + + mutex_init(&ci->fsm.lock); + + ci->fsm_timer = devm_kzalloc(ci->dev, + sizeof(struct ci_otg_fsm_timer_list), GFP_KERNEL); + if (!ci->fsm_timer) { + dev_err(ci->dev, + "Failed to allocate timer structure for ci hdrc otg!\n"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&ci->fsm_timer->active_timers); + retval = ci_otg_init_timers(ci); + if (retval) { + dev_err(ci->dev, "Couldn't init OTG timers\n"); + return retval; + } + + 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); +} diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h new file mode 100644 index 00000000000..94c085f456a --- /dev/null +++ b/drivers/usb/chipidea/otg_fsm.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Jun Li + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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_SESS_VLD (1000) + +enum ci_otg_fsm_timer_index { + /* + * CI specific timers, start from the end + * of standard and auxiliary OTG timers + */ + B_DATA_PLS = NUM_OTG_FSM_TIMERS, + B_SSEND_SRP, + B_SESS_VLD, + + NUM_CI_OTG_FSM_TIMERS, +}; + +struct ci_otg_fsm_timer { + unsigned long expires; /* Number of count increase to timeout */ + unsigned long count; /* Tick counter */ + void (*function)(void *, unsigned long); /* Timeout function */ + unsigned long data; /* Data passed to function */ + struct list_head list; +}; + +struct ci_otg_fsm_timer_list { + struct ci_otg_fsm_timer *timer_list[NUM_CI_OTG_FSM_TIMERS]; + struct list_head active_timers; +}; + +#ifdef 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 */ diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 2f45bba8561..b8125aa64ad 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -13,26 +13,22 @@ #include <linux/delay.h> #include <linux/device.h> #include <linux/dmapool.h> -#include <linux/dma-mapping.h> #include <linux/err.h> -#include <linux/init.h> -#include <linux/platform_device.h> -#include <linux/module.h> -#include <linux/interrupt.h> -#include <linux/io.h> -#include <linux/irq.h> +#include <linux/irqreturn.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/pm_runtime.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.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 "debug.h" +#include "otg.h" +#include "otg_fsm.h" /* control endpoint description */ static const struct usb_endpoint_descriptor @@ -67,7 +63,7 @@ static inline int hw_ep_bit(int num, int dir) return num + (dir ? 16 : 0); } -static inline int ep_to_bit(struct ci13xxx *ci, int n) +static inline int ep_to_bit(struct ci_hdrc *ci, int n) { int fill = 16 - ci->hw_ep_max / 2; @@ -83,15 +79,17 @@ static inline int ep_to_bit(struct ci13xxx *ci, int n) * * This function returns an error code */ -static int hw_device_state(struct ci13xxx *ci, u32 dma) +static int hw_device_state(struct ci_hdrc *ci, u32 dma) { if (dma) { hw_write(ci, OP_ENDPTLISTADDR, ~0, dma); /* interrupt, error, port change, reset, sleep/suspend */ hw_write(ci, OP_USBINTR, ~0, USBi_UI|USBi_UEI|USBi_PCI|USBi_URI|USBi_SLI); + hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); } else { hw_write(ci, OP_USBINTR, ~0, 0); + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); } return 0; } @@ -103,13 +101,13 @@ static int hw_device_state(struct ci13xxx *ci, u32 dma) * * This function returns an error code */ -static int hw_ep_flush(struct ci13xxx *ci, int num, int dir) +static int hw_ep_flush(struct ci_hdrc *ci, int num, int dir) { int n = hw_ep_bit(num, dir); do { /* flush any pending transfer */ - hw_write(ci, OP_ENDPTFLUSH, BIT(n), BIT(n)); + hw_write(ci, OP_ENDPTFLUSH, ~0, BIT(n)); while (hw_read(ci, OP_ENDPTFLUSH, BIT(n))) cpu_relax(); } while (hw_read(ci, OP_ENDPTSTAT, BIT(n))); @@ -124,7 +122,7 @@ static int hw_ep_flush(struct ci13xxx *ci, int num, int dir) * * This function returns an error code */ -static int hw_ep_disable(struct ci13xxx *ci, int num, int dir) +static int hw_ep_disable(struct ci_hdrc *ci, int num, int dir) { hw_ep_flush(ci, num, dir); hw_write(ci, OP_ENDPTCTRL + num, @@ -140,13 +138,13 @@ static int hw_ep_disable(struct ci13xxx *ci, int num, int dir) * * This function returns an error code */ -static int hw_ep_enable(struct ci13xxx *ci, int num, int dir, int type) +static int hw_ep_enable(struct ci_hdrc *ci, int num, int dir, int type) { u32 mask, data; if (dir) { mask = ENDPTCTRL_TXT; /* type */ - data = type << ffs_nr(mask); + data = type << __ffs(mask); mask |= ENDPTCTRL_TXS; /* unstall */ mask |= ENDPTCTRL_TXR; /* reset data toggle */ @@ -155,7 +153,7 @@ static int hw_ep_enable(struct ci13xxx *ci, int num, int dir, int type) data |= ENDPTCTRL_TXE; } else { mask = ENDPTCTRL_RXT; /* type */ - data = type << ffs_nr(mask); + data = type << __ffs(mask); mask |= ENDPTCTRL_RXS; /* unstall */ mask |= ENDPTCTRL_RXR; /* reset data toggle */ @@ -174,7 +172,7 @@ static int hw_ep_enable(struct ci13xxx *ci, int num, int dir, int type) * * This function returns 1 if endpoint halted */ -static int hw_ep_get_halt(struct ci13xxx *ci, int num, int dir) +static int hw_ep_get_halt(struct ci_hdrc *ci, int num, int dir) { u32 mask = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; @@ -182,19 +180,6 @@ static int hw_ep_get_halt(struct ci13xxx *ci, int num, int dir) } /** - * hw_test_and_clear_setup_status: test & clear setup status (execute without - * interruption) - * @n: endpoint number - * - * This function returns setup status - */ -static int hw_test_and_clear_setup_status(struct ci13xxx *ci, int n) -{ - n = ep_to_bit(ci, n); - return hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(n)); -} - -/** * hw_ep_prime: primes endpoint (execute without interruption) * @num: endpoint number * @dir: endpoint direction @@ -202,14 +187,14 @@ static int hw_test_and_clear_setup_status(struct ci13xxx *ci, int n) * * This function returns an error code */ -static int hw_ep_prime(struct ci13xxx *ci, int num, int dir, int is_ctrl) +static int hw_ep_prime(struct ci_hdrc *ci, int num, int dir, int is_ctrl) { int n = hw_ep_bit(num, dir); if (is_ctrl && dir == RX && hw_read(ci, OP_ENDPTSETUPSTAT, BIT(num))) return -EAGAIN; - hw_write(ci, OP_ENDPTPRIME, BIT(n), BIT(n)); + hw_write(ci, OP_ENDPTPRIME, ~0, BIT(n)); while (hw_read(ci, OP_ENDPTPRIME, BIT(n))) cpu_relax(); @@ -229,13 +214,13 @@ static int hw_ep_prime(struct ci13xxx *ci, int num, int dir, int is_ctrl) * * This function returns an error code */ -static int hw_ep_set_halt(struct ci13xxx *ci, int num, int dir, int value) +static int hw_ep_set_halt(struct ci_hdrc *ci, int num, int dir, int value) { if (value != 0 && value != 1) return -EINVAL; do { - enum ci13xxx_regs reg = OP_ENDPTCTRL + num; + enum ci_hw_regs reg = OP_ENDPTCTRL + num; u32 mask_xs = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; u32 mask_xr = dir ? ENDPTCTRL_TXR : ENDPTCTRL_RXR; @@ -252,40 +237,20 @@ static int hw_ep_set_halt(struct ci13xxx *ci, int num, int dir, int value) * * This function returns true if high speed port */ -static int hw_port_is_high_speed(struct ci13xxx *ci) +static int hw_port_is_high_speed(struct ci_hdrc *ci) { return ci->hw_bank.lpm ? hw_read(ci, OP_DEVLC, DEVLC_PSPD) : hw_read(ci, OP_PORTSC, PORTSC_HSP); } /** - * hw_read_intr_enable: returns interrupt enable register - * - * This function returns register data - */ -static u32 hw_read_intr_enable(struct ci13xxx *ci) -{ - return hw_read(ci, OP_USBINTR, ~0); -} - -/** - * hw_read_intr_status: returns interrupt status register - * - * This function returns register data - */ -static u32 hw_read_intr_status(struct ci13xxx *ci) -{ - return hw_read(ci, OP_USBSTS, ~0); -} - -/** * hw_test_and_clear_complete: test & clear complete status (execute without * interruption) * @n: endpoint number * * This function returns complete status */ -static int hw_test_and_clear_complete(struct ci13xxx *ci, int n) +static int hw_test_and_clear_complete(struct ci_hdrc *ci, int n) { n = ep_to_bit(ci, n); return hw_test_and_clear(ci, OP_ENDPTCOMPLETE, BIT(n)); @@ -297,7 +262,7 @@ static int hw_test_and_clear_complete(struct ci13xxx *ci, int n) * * This function returns active interrutps */ -static u32 hw_test_and_clear_intr_active(struct ci13xxx *ci) +static u32 hw_test_and_clear_intr_active(struct ci_hdrc *ci) { u32 reg = hw_read_intr_status(ci) & hw_read_intr_enable(ci); @@ -305,25 +270,13 @@ static u32 hw_test_and_clear_intr_active(struct ci13xxx *ci) return reg; } -static void hw_enable_vbus_intr(struct ci13xxx *ci) -{ - hw_write(ci, OP_OTGSC, OTGSC_AVVIS, OTGSC_AVVIS); - hw_write(ci, OP_OTGSC, OTGSC_AVVIE, OTGSC_AVVIE); - queue_work(ci->wq, &ci->vbus_work); -} - -static void hw_disable_vbus_intr(struct ci13xxx *ci) -{ - hw_write(ci, OP_OTGSC, OTGSC_AVVIE, 0); -} - /** * hw_test_and_clear_setup_guard: test & clear setup guard (execute without * interruption) * * This function returns guard value */ -static int hw_test_and_clear_setup_guard(struct ci13xxx *ci) +static int hw_test_and_clear_setup_guard(struct ci_hdrc *ci) { return hw_test_and_write(ci, OP_USBCMD, USBCMD_SUTW, 0); } @@ -334,7 +287,7 @@ static int hw_test_and_clear_setup_guard(struct ci13xxx *ci) * * This function returns guard value */ -static int hw_test_and_set_setup_guard(struct ci13xxx *ci) +static int hw_test_and_set_setup_guard(struct ci_hdrc *ci) { return hw_test_and_write(ci, OP_USBCMD, USBCMD_SUTW, USBCMD_SUTW); } @@ -346,10 +299,10 @@ static int hw_test_and_set_setup_guard(struct ci13xxx *ci) * This function explicitly sets the address, without the "USBADRA" (advance) * feature, which is not supported by older versions of the controller. */ -static void hw_usb_set_address(struct ci13xxx *ci, u8 value) +static void hw_usb_set_address(struct ci_hdrc *ci, u8 value) { hw_write(ci, OP_DEVICEADDR, DEVICEADDR_USBADR, - value << ffs_nr(DEVICEADDR_USBADR)); + value << __ffs(DEVICEADDR_USBADR)); } /** @@ -358,7 +311,7 @@ static void hw_usb_set_address(struct ci13xxx *ci, u8 value) * * This function returns an error code */ -static int hw_usb_reset(struct ci13xxx *ci) +static int hw_usb_reset(struct ci_hdrc *ci) { hw_usb_set_address(ci, 0); @@ -383,24 +336,71 @@ static int hw_usb_reset(struct ci13xxx *ci) return 0; } -static void vbus_work(struct work_struct *work) +/****************************************************************************** + * UTIL block + *****************************************************************************/ + +static int add_td_to_list(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, + unsigned length) { - struct ci13xxx *ci = container_of(work, struct ci13xxx, vbus_work); + int i; + u32 temp; + struct td_node *lastnode, *node = kzalloc(sizeof(struct td_node), + GFP_ATOMIC); - if (hw_read(ci, OP_OTGSC, OTGSC_AVV)) - usb_gadget_vbus_connect(&ci->gadget); - else - usb_gadget_vbus_disconnect(&ci->gadget); + if (node == NULL) + return -ENOMEM; + + node->ptr = dma_pool_alloc(hwep->td_pool, GFP_ATOMIC, + &node->dma); + if (node->ptr == NULL) { + kfree(node); + return -ENOMEM; + } + + memset(node->ptr, 0, sizeof(struct ci_hw_td)); + node->ptr->token = cpu_to_le32(length << __ffs(TD_TOTAL_BYTES)); + node->ptr->token &= cpu_to_le32(TD_TOTAL_BYTES); + node->ptr->token |= cpu_to_le32(TD_STATUS_ACTIVE); + if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == TX) { + u32 mul = hwreq->req.length / hwep->ep.maxpacket; + + if (hwreq->req.length == 0 + || hwreq->req.length % hwep->ep.maxpacket) + mul++; + node->ptr->token |= mul << __ffs(TD_MULTO); + } + + temp = (u32) (hwreq->req.dma + hwreq->req.actual); + if (length) { + node->ptr->page[0] = cpu_to_le32(temp); + for (i = 1; i < TD_PAGE_COUNT; i++) { + u32 page = temp + i * CI_HDRC_PAGE_SIZE; + page &= ~TD_RESERVED_MASK; + node->ptr->page[i] = cpu_to_le32(page); + } + } + + hwreq->req.actual += length; + + if (!list_empty(&hwreq->tds)) { + /* get the last entry */ + lastnode = list_entry(hwreq->tds.prev, + struct td_node, td); + lastnode->ptr->next = cpu_to_le32(node->dma); + } + + INIT_LIST_HEAD(&node->td); + list_add_tail(&node->td, &hwreq->tds); + + return 0; } -/****************************************************************************** - * UTIL block - *****************************************************************************/ /** * _usb_addr: calculates endpoint address from direction & number * @ep: endpoint */ -static inline u8 _usb_addr(struct ci13xxx_ep *ep) +static inline u8 _usb_addr(struct ci_hw_ep *ep) { return ((ep->dir == TX) ? USB_ENDPOINT_DIR_MASK : 0) | ep->num; } @@ -408,70 +408,73 @@ static inline u8 _usb_addr(struct ci13xxx_ep *ep) /** * _hardware_queue: configures a request at hardware level * @gadget: gadget - * @mEp: endpoint + * @hwep: endpoint * * This function returns an error code */ -static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) +static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) { - struct ci13xxx *ci = mEp->ci; - unsigned i; + struct ci_hdrc *ci = hwep->ci; int ret = 0; - unsigned length = mReq->req.length; + unsigned rest = hwreq->req.length; + int pages = TD_PAGE_COUNT; + struct td_node *firstnode, *lastnode; /* don't queue twice */ - if (mReq->req.status == -EALREADY) + if (hwreq->req.status == -EALREADY) return -EALREADY; - mReq->req.status = -EALREADY; - - if (mReq->req.zero && length && (length % mEp->ep.maxpacket == 0)) { - mReq->zptr = dma_pool_alloc(mEp->td_pool, GFP_ATOMIC, - &mReq->zdma); - if (mReq->zptr == NULL) - return -ENOMEM; + hwreq->req.status = -EALREADY; - memset(mReq->zptr, 0, sizeof(*mReq->zptr)); - mReq->zptr->next = TD_TERMINATE; - mReq->zptr->token = TD_STATUS_ACTIVE; - if (!mReq->req.no_interrupt) - mReq->zptr->token |= TD_IOC; - } - ret = usb_gadget_map_request(&ci->gadget, &mReq->req, mEp->dir); + ret = usb_gadget_map_request(&ci->gadget, &hwreq->req, hwep->dir); if (ret) return ret; /* - * TD configuration - * TODO - handle requests which spawns into several TDs + * The first buffer could be not page aligned. + * In that case we have to span into one extra td. */ - memset(mReq->ptr, 0, sizeof(*mReq->ptr)); - mReq->ptr->token = length << ffs_nr(TD_TOTAL_BYTES); - mReq->ptr->token &= TD_TOTAL_BYTES; - mReq->ptr->token |= TD_STATUS_ACTIVE; - if (mReq->zptr) { - mReq->ptr->next = mReq->zdma; - } else { - mReq->ptr->next = TD_TERMINATE; - if (!mReq->req.no_interrupt) - mReq->ptr->token |= TD_IOC; + if (hwreq->req.dma % PAGE_SIZE) + pages--; + + if (rest == 0) + add_td_to_list(hwep, hwreq, 0); + + while (rest > 0) { + unsigned count = min(hwreq->req.length - hwreq->req.actual, + (unsigned)(pages * CI_HDRC_PAGE_SIZE)); + add_td_to_list(hwep, hwreq, count); + rest -= count; } - mReq->ptr->page[0] = mReq->req.dma; - for (i = 1; i < 5; i++) - mReq->ptr->page[i] = - (mReq->req.dma + i * CI13XXX_PAGE_SIZE) & ~TD_RESERVED_MASK; - - if (!list_empty(&mEp->qh.queue)) { - struct ci13xxx_req *mReqPrev; - int n = hw_ep_bit(mEp->num, mEp->dir); + + if (hwreq->req.zero && hwreq->req.length + && (hwreq->req.length % hwep->ep.maxpacket == 0)) + add_td_to_list(hwep, hwreq, 0); + + firstnode = list_first_entry(&hwreq->tds, struct td_node, td); + + lastnode = list_entry(hwreq->tds.prev, + struct td_node, td); + + lastnode->ptr->next = cpu_to_le32(TD_TERMINATE); + if (!hwreq->req.no_interrupt) + lastnode->ptr->token |= cpu_to_le32(TD_IOC); + wmb(); + + hwreq->req.actual = 0; + if (!list_empty(&hwep->qh.queue)) { + struct ci_hw_req *hwreqprev; + int n = hw_ep_bit(hwep->num, hwep->dir); int tmp_stat; + struct td_node *prevlastnode; + u32 next = firstnode->dma & TD_ADDR_MASK; + + hwreqprev = list_entry(hwep->qh.queue.prev, + struct ci_hw_req, queue); + prevlastnode = list_entry(hwreqprev->tds.prev, + struct td_node, td); - mReqPrev = list_entry(mEp->qh.queue.prev, - struct ci13xxx_req, queue); - if (mReqPrev->zptr) - mReqPrev->zptr->next = mReq->dma & TD_ADDR_MASK; - else - mReqPrev->ptr->next = mReq->dma & TD_ADDR_MASK; + prevlastnode->ptr->next = cpu_to_le32(next); wmb(); if (hw_read(ci, OP_ENDPTPRIME, BIT(n))) goto done; @@ -485,91 +488,153 @@ static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) } /* QH configuration */ - mEp->qh.ptr->td.next = mReq->dma; /* TERMINATE = 0 */ - mEp->qh.ptr->td.token &= ~TD_STATUS; /* clear status */ - mEp->qh.ptr->cap |= QH_ZLT; + hwep->qh.ptr->td.next = cpu_to_le32(firstnode->dma); + hwep->qh.ptr->td.token &= + cpu_to_le32(~(TD_STATUS_HALTED|TD_STATUS_ACTIVE)); + + if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == RX) { + u32 mul = hwreq->req.length / hwep->ep.maxpacket; + + if (hwreq->req.length == 0 + || hwreq->req.length % hwep->ep.maxpacket) + mul++; + hwep->qh.ptr->cap |= mul << __ffs(QH_MULT); + } wmb(); /* synchronize before ep prime */ - ret = hw_ep_prime(ci, mEp->num, mEp->dir, - mEp->type == USB_ENDPOINT_XFER_CONTROL); + ret = hw_ep_prime(ci, hwep->num, hwep->dir, + hwep->type == USB_ENDPOINT_XFER_CONTROL); done: return ret; } +/* + * free_pending_td: remove a pending request for the endpoint + * @hwep: endpoint + */ +static void free_pending_td(struct ci_hw_ep *hwep) +{ + struct td_node *pending = hwep->pending_td; + + dma_pool_free(hwep->td_pool, pending->ptr, pending->dma); + hwep->pending_td = NULL; + kfree(pending); +} + /** * _hardware_dequeue: handles a request at hardware level * @gadget: gadget - * @mEp: endpoint + * @hwep: endpoint * * This function returns an error code */ -static int _hardware_dequeue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) +static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) { - if (mReq->req.status != -EALREADY) + u32 tmptoken; + struct td_node *node, *tmpnode; + unsigned remaining_length; + unsigned actual = hwreq->req.length; + + if (hwreq->req.status != -EALREADY) return -EINVAL; - if ((TD_STATUS_ACTIVE & mReq->ptr->token) != 0) - return -EBUSY; + hwreq->req.status = 0; - if (mReq->zptr) { - if ((TD_STATUS_ACTIVE & mReq->zptr->token) != 0) + list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) { + tmptoken = le32_to_cpu(node->ptr->token); + if ((TD_STATUS_ACTIVE & tmptoken) != 0) { + hwreq->req.status = -EALREADY; return -EBUSY; - dma_pool_free(mEp->td_pool, mReq->zptr, mReq->zdma); - mReq->zptr = NULL; - } + } - mReq->req.status = 0; + remaining_length = (tmptoken & TD_TOTAL_BYTES); + remaining_length >>= __ffs(TD_TOTAL_BYTES); + actual -= remaining_length; - usb_gadget_unmap_request(&mEp->ci->gadget, &mReq->req, mEp->dir); + hwreq->req.status = tmptoken & TD_STATUS; + if ((TD_STATUS_HALTED & hwreq->req.status)) { + hwreq->req.status = -EPIPE; + break; + } else if ((TD_STATUS_DT_ERR & hwreq->req.status)) { + hwreq->req.status = -EPROTO; + break; + } else if ((TD_STATUS_TR_ERR & hwreq->req.status)) { + hwreq->req.status = -EILSEQ; + break; + } - mReq->req.status = mReq->ptr->token & TD_STATUS; - if ((TD_STATUS_HALTED & mReq->req.status) != 0) - mReq->req.status = -1; - else if ((TD_STATUS_DT_ERR & mReq->req.status) != 0) - mReq->req.status = -1; - else if ((TD_STATUS_TR_ERR & mReq->req.status) != 0) - mReq->req.status = -1; + if (remaining_length) { + if (hwep->dir) { + hwreq->req.status = -EPROTO; + break; + } + } + /* + * As the hardware could still address the freed td + * which will run the udc unusable, the cleanup of the + * td has to be delayed by one. + */ + if (hwep->pending_td) + free_pending_td(hwep); + + hwep->pending_td = node; + list_del_init(&node->td); + } - mReq->req.actual = mReq->ptr->token & TD_TOTAL_BYTES; - mReq->req.actual >>= ffs_nr(TD_TOTAL_BYTES); - mReq->req.actual = mReq->req.length - mReq->req.actual; - mReq->req.actual = mReq->req.status ? 0 : mReq->req.actual; + usb_gadget_unmap_request(&hwep->ci->gadget, &hwreq->req, hwep->dir); - return mReq->req.actual; + hwreq->req.actual += actual; + + if (hwreq->req.status) + return hwreq->req.status; + + return hwreq->req.actual; } /** * _ep_nuke: dequeues all endpoint requests - * @mEp: endpoint + * @hwep: endpoint * * This function returns an error code * Caller must hold lock */ -static int _ep_nuke(struct ci13xxx_ep *mEp) -__releases(mEp->lock) -__acquires(mEp->lock) +static int _ep_nuke(struct ci_hw_ep *hwep) +__releases(hwep->lock) +__acquires(hwep->lock) { - if (mEp == NULL) + struct td_node *node, *tmpnode; + if (hwep == NULL) return -EINVAL; - hw_ep_flush(mEp->ci, mEp->num, mEp->dir); + hw_ep_flush(hwep->ci, hwep->num, hwep->dir); - while (!list_empty(&mEp->qh.queue)) { + while (!list_empty(&hwep->qh.queue)) { /* pop oldest request */ - struct ci13xxx_req *mReq = \ - list_entry(mEp->qh.queue.next, - struct ci13xxx_req, queue); - list_del_init(&mReq->queue); - mReq->req.status = -ESHUTDOWN; - - if (mReq->req.complete != NULL) { - spin_unlock(mEp->lock); - mReq->req.complete(&mEp->ep, &mReq->req); - spin_lock(mEp->lock); + struct ci_hw_req *hwreq = list_entry(hwep->qh.queue.next, + struct ci_hw_req, queue); + + list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) { + dma_pool_free(hwep->td_pool, node->ptr, node->dma); + list_del_init(&node->td); + node->ptr = NULL; + kfree(node); + } + + list_del_init(&hwreq->queue); + hwreq->req.status = -ESHUTDOWN; + + if (hwreq->req.complete != NULL) { + spin_unlock(hwep->lock); + hwreq->req.complete(&hwep->ep, &hwreq->req); + spin_lock(hwep->lock); } } + + if (hwep->pending_td) + free_pending_td(hwep); + return 0; } @@ -582,7 +647,7 @@ __acquires(mEp->lock) static int _gadget_stop_activity(struct usb_gadget *gadget) { struct usb_ep *ep; - struct ci13xxx *ci = container_of(gadget, struct ci13xxx, gadget); + struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); unsigned long flags; spin_lock_irqsave(&ci->lock, flags); @@ -598,9 +663,6 @@ static int _gadget_stop_activity(struct usb_gadget *gadget) usb_ep_fifo_flush(&ci->ep0out->ep); usb_ep_fifo_flush(&ci->ep0in->ep); - if (ci->driver) - ci->driver->disconnect(gadget); - /* make sure to disable all endpoints */ gadget_for_each_ep(ep, gadget) { usb_ep_disable(ep); @@ -623,15 +685,18 @@ static int _gadget_stop_activity(struct usb_gadget *gadget) * * This function resets USB engine after a bus reset occurred */ -static void isr_reset_handler(struct ci13xxx *ci) +static void isr_reset_handler(struct ci_hdrc *ci) __releases(ci->lock) __acquires(ci->lock) { int retval; - dbg_event(0xFF, "BUS RST", 0); - spin_unlock(&ci->lock); + if (ci->gadget.speed != USB_SPEED_UNKNOWN) { + if (ci->driver) + ci->driver->disconnect(&ci->gadget); + } + retval = _gadget_stop_activity(&ci->gadget); if (retval) goto done; @@ -644,6 +709,8 @@ __acquires(ci->lock) if (ci->status == NULL) retval = -ENOMEM; + usb_gadget_set_state(&ci->gadget, USB_STATE_DEFAULT); + done: spin_lock(&ci->lock); @@ -668,28 +735,82 @@ static void isr_get_status_complete(struct usb_ep *ep, struct usb_request *req) } /** + * _ep_queue: queues (submits) an I/O request to an endpoint + * + * Caller must hold lock + */ +static int _ep_queue(struct usb_ep *ep, struct usb_request *req, + gfp_t __maybe_unused gfp_flags) +{ + struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); + struct ci_hw_req *hwreq = container_of(req, struct ci_hw_req, req); + struct ci_hdrc *ci = hwep->ci; + int retval = 0; + + if (ep == NULL || req == NULL || hwep->ep.desc == NULL) + return -EINVAL; + + if (hwep->type == USB_ENDPOINT_XFER_CONTROL) { + if (req->length) + hwep = (ci->ep0_dir == RX) ? + ci->ep0out : ci->ep0in; + if (!list_empty(&hwep->qh.queue)) { + _ep_nuke(hwep); + retval = -EOVERFLOW; + dev_warn(hwep->ci->dev, "endpoint ctrl %X nuked\n", + _usb_addr(hwep)); + } + } + + if (usb_endpoint_xfer_isoc(hwep->ep.desc) && + hwreq->req.length > (1 + hwep->ep.mult) * hwep->ep.maxpacket) { + dev_err(hwep->ci->dev, "request length too big for isochronous\n"); + return -EMSGSIZE; + } + + /* first nuke then test link, e.g. previous status has not sent */ + if (!list_empty(&hwreq->queue)) { + dev_err(hwep->ci->dev, "request already in queue\n"); + return -EBUSY; + } + + /* push request */ + hwreq->req.status = -EINPROGRESS; + hwreq->req.actual = 0; + + retval = _hardware_enqueue(hwep, hwreq); + + if (retval == -EALREADY) + retval = 0; + if (!retval) + list_add_tail(&hwreq->queue, &hwep->qh.queue); + + return retval; +} + +/** * isr_get_status_response: get_status request response * @ci: ci struct * @setup: setup request packet * * This function returns an error code */ -static int isr_get_status_response(struct ci13xxx *ci, +static int isr_get_status_response(struct ci_hdrc *ci, struct usb_ctrlrequest *setup) -__releases(mEp->lock) -__acquires(mEp->lock) +__releases(hwep->lock) +__acquires(hwep->lock) { - struct ci13xxx_ep *mEp = ci->ep0in; + struct ci_hw_ep *hwep = ci->ep0in; struct usb_request *req = NULL; gfp_t gfp_flags = GFP_ATOMIC; int dir, num, retval; - if (mEp == NULL || setup == NULL) + if (hwep == NULL || setup == NULL) return -EINVAL; - spin_unlock(mEp->lock); - req = usb_ep_alloc_request(&mEp->ep, gfp_flags); - spin_lock(mEp->lock); + spin_unlock(hwep->lock); + req = usb_ep_alloc_request(&hwep->ep, gfp_flags); + spin_lock(hwep->lock); if (req == NULL) return -ENOMEM; @@ -704,7 +825,6 @@ __acquires(mEp->lock) if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { /* Assume that device is bus powered for now. */ *(u16 *)req->buf = ci->remote_wakeup << 1; - retval = 0; } else if ((setup->bRequestType & USB_RECIP_MASK) \ == USB_RECIP_ENDPOINT) { dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ? @@ -714,9 +834,7 @@ __acquires(mEp->lock) } /* else do nothing; reserved for future use */ - spin_unlock(mEp->lock); - retval = usb_ep_queue(&mEp->ep, req, gfp_flags); - spin_lock(mEp->lock); + retval = _ep_queue(&hwep->ep, req, gfp_flags); if (retval) goto err_free_buf; @@ -725,9 +843,9 @@ __acquires(mEp->lock) err_free_buf: kfree(req->buf); err_free_req: - spin_unlock(mEp->lock); - usb_ep_free_request(&mEp->ep, req); - spin_lock(mEp->lock); + spin_unlock(hwep->lock); + usb_ep_free_request(&hwep->ep, req); + spin_lock(hwep->lock); return retval; } @@ -742,12 +860,14 @@ __acquires(mEp->lock) static void isr_setup_status_complete(struct usb_ep *ep, struct usb_request *req) { - struct ci13xxx *ci = req->context; + struct ci_hdrc *ci = req->context; unsigned long flags; if (ci->setaddr) { hw_usb_set_address(ci, ci->address); ci->setaddr = false; + if (ci->address) + usb_gadget_set_state(&ci->gadget, USB_STATE_ADDRESS); } spin_lock_irqsave(&ci->lock, flags); @@ -762,248 +882,253 @@ isr_setup_status_complete(struct usb_ep *ep, struct usb_request *req) * * This function returns an error code */ -static int isr_setup_status_phase(struct ci13xxx *ci) -__releases(mEp->lock) -__acquires(mEp->lock) +static int isr_setup_status_phase(struct ci_hdrc *ci) { int retval; - struct ci13xxx_ep *mEp; + struct ci_hw_ep *hwep; - mEp = (ci->ep0_dir == TX) ? ci->ep0out : ci->ep0in; + hwep = (ci->ep0_dir == TX) ? ci->ep0out : ci->ep0in; ci->status->context = ci; ci->status->complete = isr_setup_status_complete; - spin_unlock(mEp->lock); - retval = usb_ep_queue(&mEp->ep, ci->status, GFP_ATOMIC); - spin_lock(mEp->lock); + retval = _ep_queue(&hwep->ep, ci->status, GFP_ATOMIC); return retval; } /** * isr_tr_complete_low: transaction complete low level handler - * @mEp: endpoint + * @hwep: endpoint * * This function returns an error code * Caller must hold lock */ -static int isr_tr_complete_low(struct ci13xxx_ep *mEp) -__releases(mEp->lock) -__acquires(mEp->lock) +static int isr_tr_complete_low(struct ci_hw_ep *hwep) +__releases(hwep->lock) +__acquires(hwep->lock) { - struct ci13xxx_req *mReq, *mReqTemp; - struct ci13xxx_ep *mEpTemp = mEp; + struct ci_hw_req *hwreq, *hwreqtemp; + struct ci_hw_ep *hweptemp = hwep; int retval = 0; - list_for_each_entry_safe(mReq, mReqTemp, &mEp->qh.queue, + list_for_each_entry_safe(hwreq, hwreqtemp, &hwep->qh.queue, queue) { - retval = _hardware_dequeue(mEp, mReq); + retval = _hardware_dequeue(hwep, hwreq); if (retval < 0) break; - list_del_init(&mReq->queue); - dbg_done(_usb_addr(mEp), mReq->ptr->token, retval); - if (mReq->req.complete != NULL) { - spin_unlock(mEp->lock); - if ((mEp->type == USB_ENDPOINT_XFER_CONTROL) && - mReq->req.length) - mEpTemp = mEp->ci->ep0in; - mReq->req.complete(&mEpTemp->ep, &mReq->req); - spin_lock(mEp->lock); + list_del_init(&hwreq->queue); + if (hwreq->req.complete != NULL) { + spin_unlock(hwep->lock); + if ((hwep->type == USB_ENDPOINT_XFER_CONTROL) && + hwreq->req.length) + hweptemp = hwep->ci->ep0in; + hwreq->req.complete(&hweptemp->ep, &hwreq->req); + spin_lock(hwep->lock); } } if (retval == -EBUSY) retval = 0; - if (retval < 0) - dbg_event(_usb_addr(mEp), "DONE", retval); return retval; } /** - * isr_tr_complete_handler: transaction complete interrupt handler + * isr_setup_packet_handler: setup packet handler * @ci: UDC descriptor * - * This function handles traffic events + * This function handles setup packet */ -static void isr_tr_complete_handler(struct ci13xxx *ci) +static void isr_setup_packet_handler(struct ci_hdrc *ci) __releases(ci->lock) __acquires(ci->lock) { - unsigned i; + struct ci_hw_ep *hwep = &ci->ci_hw_ep[0]; + struct usb_ctrlrequest req; + int type, num, dir, err = -EINVAL; u8 tmode = 0; - for (i = 0; i < ci->hw_ep_max; i++) { - struct ci13xxx_ep *mEp = &ci->ci13xxx_ep[i]; - int type, num, dir, err = -EINVAL; - struct usb_ctrlrequest req; - - if (mEp->ep.desc == NULL) - continue; /* not configured */ - - if (hw_test_and_clear_complete(ci, i)) { - err = isr_tr_complete_low(mEp); - if (mEp->type == USB_ENDPOINT_XFER_CONTROL) { - if (err > 0) /* needs status phase */ - err = isr_setup_status_phase(ci); - if (err < 0) { - dbg_event(_usb_addr(mEp), - "ERROR", err); - spin_unlock(&ci->lock); - if (usb_ep_set_halt(&mEp->ep)) - dev_err(ci->dev, - "error: ep_set_halt\n"); - spin_lock(&ci->lock); - } - } - } - - if (mEp->type != USB_ENDPOINT_XFER_CONTROL || - !hw_test_and_clear_setup_status(ci, i)) - continue; - - if (i != 0) { - dev_warn(ci->dev, "ctrl traffic at endpoint %d\n", i); - continue; - } - - /* - * Flush data and handshake transactions of previous - * setup packet. - */ - _ep_nuke(ci->ep0out); - _ep_nuke(ci->ep0in); - - /* read_setup_packet */ - do { - hw_test_and_set_setup_guard(ci); - memcpy(&req, &mEp->qh.ptr->setup, sizeof(req)); - } while (!hw_test_and_clear_setup_guard(ci)); + /* + * Flush data and handshake transactions of previous + * setup packet. + */ + _ep_nuke(ci->ep0out); + _ep_nuke(ci->ep0in); - type = req.bRequestType; + /* read_setup_packet */ + do { + hw_test_and_set_setup_guard(ci); + memcpy(&req, &hwep->qh.ptr->setup, sizeof(req)); + } while (!hw_test_and_clear_setup_guard(ci)); - ci->ep0_dir = (type & USB_DIR_IN) ? TX : RX; + type = req.bRequestType; - dbg_setup(_usb_addr(mEp), &req); + ci->ep0_dir = (type & USB_DIR_IN) ? TX : RX; - switch (req.bRequest) { - case USB_REQ_CLEAR_FEATURE: - if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && - le16_to_cpu(req.wValue) == - USB_ENDPOINT_HALT) { - if (req.wLength != 0) - break; - num = le16_to_cpu(req.wIndex); - dir = num & USB_ENDPOINT_DIR_MASK; - num &= USB_ENDPOINT_NUMBER_MASK; - if (dir) /* TX */ - num += ci->hw_ep_max/2; - if (!ci->ci13xxx_ep[num].wedge) { - spin_unlock(&ci->lock); - err = usb_ep_clear_halt( - &ci->ci13xxx_ep[num].ep); - spin_lock(&ci->lock); - if (err) - break; - } - err = isr_setup_status_phase(ci); - } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE) && - le16_to_cpu(req.wValue) == - USB_DEVICE_REMOTE_WAKEUP) { - if (req.wLength != 0) + switch (req.bRequest) { + case USB_REQ_CLEAR_FEATURE: + if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && + le16_to_cpu(req.wValue) == + USB_ENDPOINT_HALT) { + if (req.wLength != 0) + break; + num = le16_to_cpu(req.wIndex); + dir = num & USB_ENDPOINT_DIR_MASK; + num &= USB_ENDPOINT_NUMBER_MASK; + if (dir) /* TX */ + num += ci->hw_ep_max / 2; + if (!ci->ci_hw_ep[num].wedge) { + spin_unlock(&ci->lock); + err = usb_ep_clear_halt( + &ci->ci_hw_ep[num].ep); + spin_lock(&ci->lock); + if (err) break; - ci->remote_wakeup = 0; - err = isr_setup_status_phase(ci); - } else { - goto delegate; } - break; - case USB_REQ_GET_STATUS: - if (type != (USB_DIR_IN|USB_RECIP_DEVICE) && - type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && - type != (USB_DIR_IN|USB_RECIP_INTERFACE)) - goto delegate; - if (le16_to_cpu(req.wLength) != 2 || - le16_to_cpu(req.wValue) != 0) - break; - err = isr_get_status_response(ci, &req); - break; - case USB_REQ_SET_ADDRESS: - if (type != (USB_DIR_OUT|USB_RECIP_DEVICE)) - goto delegate; - if (le16_to_cpu(req.wLength) != 0 || - le16_to_cpu(req.wIndex) != 0) + err = isr_setup_status_phase(ci); + } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE) && + le16_to_cpu(req.wValue) == + USB_DEVICE_REMOTE_WAKEUP) { + if (req.wLength != 0) break; - ci->address = (u8)le16_to_cpu(req.wValue); - ci->setaddr = true; + ci->remote_wakeup = 0; err = isr_setup_status_phase(ci); + } else { + goto delegate; + } + break; + case USB_REQ_GET_STATUS: + if (type != (USB_DIR_IN|USB_RECIP_DEVICE) && + type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && + type != (USB_DIR_IN|USB_RECIP_INTERFACE)) + goto delegate; + if (le16_to_cpu(req.wLength) != 2 || + le16_to_cpu(req.wValue) != 0) break; - case USB_REQ_SET_FEATURE: - if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && - le16_to_cpu(req.wValue) == - USB_ENDPOINT_HALT) { - if (req.wLength != 0) - break; - num = le16_to_cpu(req.wIndex); - dir = num & USB_ENDPOINT_DIR_MASK; - num &= USB_ENDPOINT_NUMBER_MASK; - if (dir) /* TX */ - num += ci->hw_ep_max/2; + err = isr_get_status_response(ci, &req); + break; + case USB_REQ_SET_ADDRESS: + if (type != (USB_DIR_OUT|USB_RECIP_DEVICE)) + goto delegate; + if (le16_to_cpu(req.wLength) != 0 || + le16_to_cpu(req.wIndex) != 0) + break; + ci->address = (u8)le16_to_cpu(req.wValue); + ci->setaddr = true; + err = isr_setup_status_phase(ci); + break; + case USB_REQ_SET_FEATURE: + if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && + le16_to_cpu(req.wValue) == + USB_ENDPOINT_HALT) { + if (req.wLength != 0) + break; + num = le16_to_cpu(req.wIndex); + dir = num & USB_ENDPOINT_DIR_MASK; + num &= USB_ENDPOINT_NUMBER_MASK; + if (dir) /* TX */ + num += ci->hw_ep_max / 2; - spin_unlock(&ci->lock); - err = usb_ep_set_halt(&ci->ci13xxx_ep[num].ep); - spin_lock(&ci->lock); - if (!err) - isr_setup_status_phase(ci); - } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE)) { - if (req.wLength != 0) - break; - switch (le16_to_cpu(req.wValue)) { - case USB_DEVICE_REMOTE_WAKEUP: - ci->remote_wakeup = 1; - err = isr_setup_status_phase(ci); + spin_unlock(&ci->lock); + err = usb_ep_set_halt(&ci->ci_hw_ep[num].ep); + spin_lock(&ci->lock); + if (!err) + isr_setup_status_phase(ci); + } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE)) { + if (req.wLength != 0) + break; + switch (le16_to_cpu(req.wValue)) { + case USB_DEVICE_REMOTE_WAKEUP: + ci->remote_wakeup = 1; + err = isr_setup_status_phase(ci); + break; + case USB_DEVICE_TEST_MODE: + tmode = le16_to_cpu(req.wIndex) >> 8; + switch (tmode) { + case TEST_J: + case TEST_K: + case TEST_SE0_NAK: + case TEST_PACKET: + case TEST_FORCE_EN: + ci->test_mode = tmode; + err = isr_setup_status_phase( + ci); break; - case USB_DEVICE_TEST_MODE: - tmode = le16_to_cpu(req.wIndex) >> 8; - switch (tmode) { - case TEST_J: - case TEST_K: - case TEST_SE0_NAK: - case TEST_PACKET: - case TEST_FORCE_EN: - ci->test_mode = tmode; - err = isr_setup_status_phase( - ci); - break; - default: - break; - } default: - goto delegate; + break; + } + break; + case USB_DEVICE_B_HNP_ENABLE: + if (ci_otg_is_fsm_mode(ci)) { + ci->gadget.b_hnp_enable = 1; + err = isr_setup_status_phase( + ci); } - } else { + break; + default: goto delegate; } - break; - default: + } else { + goto delegate; + } + break; + default: delegate: - if (req.wLength == 0) /* no data phase */ - ci->ep0_dir = TX; + if (req.wLength == 0) /* no data phase */ + ci->ep0_dir = TX; - spin_unlock(&ci->lock); - err = ci->driver->setup(&ci->gadget, &req); - spin_lock(&ci->lock); - break; - } + spin_unlock(&ci->lock); + err = ci->driver->setup(&ci->gadget, &req); + spin_lock(&ci->lock); + break; + } - if (err < 0) { - dbg_event(_usb_addr(mEp), "ERROR", err); + if (err < 0) { + spin_unlock(&ci->lock); + if (usb_ep_set_halt(&hwep->ep)) + dev_err(ci->dev, "error: ep_set_halt\n"); + spin_lock(&ci->lock); + } +} - spin_unlock(&ci->lock); - if (usb_ep_set_halt(&mEp->ep)) - dev_err(ci->dev, "error: ep_set_halt\n"); - spin_lock(&ci->lock); +/** + * isr_tr_complete_handler: transaction complete interrupt handler + * @ci: UDC descriptor + * + * This function handles traffic events + */ +static void isr_tr_complete_handler(struct ci_hdrc *ci) +__releases(ci->lock) +__acquires(ci->lock) +{ + unsigned i; + int err; + + for (i = 0; i < ci->hw_ep_max; i++) { + struct ci_hw_ep *hwep = &ci->ci_hw_ep[i]; + + if (hwep->ep.desc == NULL) + continue; /* not configured */ + + if (hw_test_and_clear_complete(ci, i)) { + err = isr_tr_complete_low(hwep); + if (hwep->type == USB_ENDPOINT_XFER_CONTROL) { + if (err > 0) /* needs status phase */ + err = isr_setup_status_phase(ci); + if (err < 0) { + spin_unlock(&ci->lock); + if (usb_ep_set_halt(&hwep->ep)) + dev_err(ci->dev, + "error: ep_set_halt\n"); + spin_lock(&ci->lock); + } + } } + + /* Only handle setup packet below */ + if (i == 0 && + hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(0))) + isr_setup_packet_handler(ci); } } @@ -1018,51 +1143,60 @@ delegate: static int ep_enable(struct usb_ep *ep, const struct usb_endpoint_descriptor *desc) { - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); int retval = 0; unsigned long flags; + u32 cap = 0; if (ep == NULL || desc == NULL) return -EINVAL; - spin_lock_irqsave(mEp->lock, flags); + spin_lock_irqsave(hwep->lock, flags); /* only internal SW should enable ctrl endpts */ - mEp->ep.desc = desc; + hwep->ep.desc = desc; + + if (!list_empty(&hwep->qh.queue)) + dev_warn(hwep->ci->dev, "enabling a non-empty endpoint!\n"); - if (!list_empty(&mEp->qh.queue)) - dev_warn(mEp->ci->dev, "enabling a non-empty endpoint!\n"); + hwep->dir = usb_endpoint_dir_in(desc) ? TX : RX; + hwep->num = usb_endpoint_num(desc); + hwep->type = usb_endpoint_type(desc); - mEp->dir = usb_endpoint_dir_in(desc) ? TX : RX; - mEp->num = usb_endpoint_num(desc); - mEp->type = usb_endpoint_type(desc); + hwep->ep.maxpacket = usb_endpoint_maxp(desc) & 0x07ff; + hwep->ep.mult = QH_ISO_MULT(usb_endpoint_maxp(desc)); - mEp->ep.maxpacket = usb_endpoint_maxp(desc); + if (hwep->type == USB_ENDPOINT_XFER_CONTROL) + cap |= QH_IOS; - dbg_event(_usb_addr(mEp), "ENABLE", 0); + cap |= QH_ZLT; + cap |= (hwep->ep.maxpacket << __ffs(QH_MAX_PKT)) & QH_MAX_PKT; + /* + * For ISO-TX, we set mult at QH as the largest value, and use + * MultO at TD as real mult value. + */ + if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == TX) + cap |= 3 << __ffs(QH_MULT); - mEp->qh.ptr->cap = 0; + hwep->qh.ptr->cap = cpu_to_le32(cap); - if (mEp->type == USB_ENDPOINT_XFER_CONTROL) - mEp->qh.ptr->cap |= QH_IOS; - else if (mEp->type == USB_ENDPOINT_XFER_ISOC) - mEp->qh.ptr->cap &= ~QH_MULT; - else - mEp->qh.ptr->cap &= ~QH_ZLT; + hwep->qh.ptr->td.next |= cpu_to_le32(TD_TERMINATE); /* needed? */ - mEp->qh.ptr->cap |= - (mEp->ep.maxpacket << ffs_nr(QH_MAX_PKT)) & QH_MAX_PKT; - mEp->qh.ptr->td.next |= TD_TERMINATE; /* needed? */ + if (hwep->num != 0 && hwep->type == USB_ENDPOINT_XFER_CONTROL) { + dev_err(hwep->ci->dev, "Set control xfer at non-ep0\n"); + retval = -EINVAL; + } /* * Enable endpoints in the HW other than ep0 as ep0 * is always enabled */ - if (mEp->num) - retval |= hw_ep_enable(mEp->ci, mEp->num, mEp->dir, mEp->type); + if (hwep->num) + retval |= hw_ep_enable(hwep->ci, hwep->num, hwep->dir, + hwep->type); - spin_unlock_irqrestore(mEp->lock, flags); + spin_unlock_irqrestore(hwep->lock, flags); return retval; } @@ -1073,34 +1207,32 @@ static int ep_enable(struct usb_ep *ep, */ static int ep_disable(struct usb_ep *ep) { - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); int direction, retval = 0; unsigned long flags; if (ep == NULL) return -EINVAL; - else if (mEp->ep.desc == NULL) + else if (hwep->ep.desc == NULL) return -EBUSY; - spin_lock_irqsave(mEp->lock, flags); + spin_lock_irqsave(hwep->lock, flags); /* only internal SW should disable ctrl endpts */ - direction = mEp->dir; + direction = hwep->dir; do { - dbg_event(_usb_addr(mEp), "DISABLE", 0); - - retval |= _ep_nuke(mEp); - retval |= hw_ep_disable(mEp->ci, mEp->num, mEp->dir); + retval |= _ep_nuke(hwep); + retval |= hw_ep_disable(hwep->ci, hwep->num, hwep->dir); - if (mEp->type == USB_ENDPOINT_XFER_CONTROL) - mEp->dir = (mEp->dir == TX) ? RX : TX; + if (hwep->type == USB_ENDPOINT_XFER_CONTROL) + hwep->dir = (hwep->dir == TX) ? RX : TX; - } while (mEp->dir != direction); + } while (hwep->dir != direction); - mEp->ep.desc = NULL; + hwep->ep.desc = NULL; - spin_unlock_irqrestore(mEp->lock, flags); + spin_unlock_irqrestore(hwep->lock, flags); return retval; } @@ -1111,27 +1243,18 @@ static int ep_disable(struct usb_ep *ep) */ static struct usb_request *ep_alloc_request(struct usb_ep *ep, gfp_t gfp_flags) { - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); - struct ci13xxx_req *mReq = NULL; + struct ci_hw_req *hwreq = NULL; if (ep == NULL) return NULL; - mReq = kzalloc(sizeof(struct ci13xxx_req), gfp_flags); - if (mReq != NULL) { - INIT_LIST_HEAD(&mReq->queue); - - mReq->ptr = dma_pool_alloc(mEp->td_pool, gfp_flags, - &mReq->dma); - if (mReq->ptr == NULL) { - kfree(mReq); - mReq = NULL; - } + hwreq = kzalloc(sizeof(struct ci_hw_req), gfp_flags); + if (hwreq != NULL) { + INIT_LIST_HEAD(&hwreq->queue); + INIT_LIST_HEAD(&hwreq->tds); } - dbg_event(_usb_addr(mEp), "ALLOC", mReq == NULL); - - return (mReq == NULL) ? NULL : &mReq->req; + return (hwreq == NULL) ? NULL : &hwreq->req; } /** @@ -1141,26 +1264,30 @@ static struct usb_request *ep_alloc_request(struct usb_ep *ep, gfp_t gfp_flags) */ static void ep_free_request(struct usb_ep *ep, struct usb_request *req) { - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); - struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); + struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); + struct ci_hw_req *hwreq = container_of(req, struct ci_hw_req, req); + struct td_node *node, *tmpnode; unsigned long flags; if (ep == NULL || req == NULL) { return; - } else if (!list_empty(&mReq->queue)) { - dev_err(mEp->ci->dev, "freeing queued request\n"); + } else if (!list_empty(&hwreq->queue)) { + dev_err(hwep->ci->dev, "freeing queued request\n"); return; } - spin_lock_irqsave(mEp->lock, flags); + spin_lock_irqsave(hwep->lock, flags); - if (mReq->ptr) - dma_pool_free(mEp->td_pool, mReq->ptr, mReq->dma); - kfree(mReq); + list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) { + dma_pool_free(hwep->td_pool, node->ptr, node->dma); + list_del_init(&node->td); + node->ptr = NULL; + kfree(node); + } - dbg_event(_usb_addr(mEp), "FREE", 0); + kfree(hwreq); - spin_unlock_irqrestore(mEp->lock, flags); + spin_unlock_irqrestore(hwep->lock, flags); } /** @@ -1171,59 +1298,16 @@ static void ep_free_request(struct usb_ep *ep, struct usb_request *req) static int ep_queue(struct usb_ep *ep, struct usb_request *req, gfp_t __maybe_unused gfp_flags) { - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); - struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); - struct ci13xxx *ci = mEp->ci; + struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); int retval = 0; unsigned long flags; - if (ep == NULL || req == NULL || mEp->ep.desc == NULL) + if (ep == NULL || req == NULL || hwep->ep.desc == NULL) return -EINVAL; - spin_lock_irqsave(mEp->lock, flags); - - if (mEp->type == USB_ENDPOINT_XFER_CONTROL) { - if (req->length) - mEp = (ci->ep0_dir == RX) ? - ci->ep0out : ci->ep0in; - if (!list_empty(&mEp->qh.queue)) { - _ep_nuke(mEp); - retval = -EOVERFLOW; - dev_warn(mEp->ci->dev, "endpoint ctrl %X nuked\n", - _usb_addr(mEp)); - } - } - - /* first nuke then test link, e.g. previous status has not sent */ - if (!list_empty(&mReq->queue)) { - retval = -EBUSY; - dev_err(mEp->ci->dev, "request already in queue\n"); - goto done; - } - - if (req->length > 4 * CI13XXX_PAGE_SIZE) { - req->length = 4 * CI13XXX_PAGE_SIZE; - retval = -EMSGSIZE; - dev_warn(mEp->ci->dev, "request length truncated\n"); - } - - dbg_queue(_usb_addr(mEp), req, retval); - - /* push request */ - mReq->req.status = -EINPROGRESS; - mReq->req.actual = 0; - - retval = _hardware_enqueue(mEp, mReq); - - if (retval == -EALREADY) { - dbg_event(_usb_addr(mEp), "QUEUE", retval); - retval = 0; - } - if (!retval) - list_add_tail(&mReq->queue, &mEp->qh.queue); - - done: - spin_unlock_irqrestore(mEp->lock, flags); + spin_lock_irqsave(hwep->lock, flags); + retval = _ep_queue(ep, req, gfp_flags); + spin_unlock_irqrestore(hwep->lock, flags); return retval; } @@ -1234,35 +1318,40 @@ static int ep_queue(struct usb_ep *ep, struct usb_request *req, */ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req) { - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); - struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); + struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); + struct ci_hw_req *hwreq = container_of(req, struct ci_hw_req, req); unsigned long flags; + struct td_node *node, *tmpnode; - if (ep == NULL || req == NULL || mReq->req.status != -EALREADY || - mEp->ep.desc == NULL || list_empty(&mReq->queue) || - list_empty(&mEp->qh.queue)) + if (ep == NULL || req == NULL || hwreq->req.status != -EALREADY || + hwep->ep.desc == NULL || list_empty(&hwreq->queue) || + list_empty(&hwep->qh.queue)) return -EINVAL; - spin_lock_irqsave(mEp->lock, flags); + spin_lock_irqsave(hwep->lock, flags); - dbg_event(_usb_addr(mEp), "DEQUEUE", 0); + hw_ep_flush(hwep->ci, hwep->num, hwep->dir); - hw_ep_flush(mEp->ci, mEp->num, mEp->dir); + list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) { + dma_pool_free(hwep->td_pool, node->ptr, node->dma); + list_del(&node->td); + kfree(node); + } /* pop request */ - list_del_init(&mReq->queue); + list_del_init(&hwreq->queue); - usb_gadget_unmap_request(&mEp->ci->gadget, req, mEp->dir); + usb_gadget_unmap_request(&hwep->ci->gadget, req, hwep->dir); req->status = -ECONNRESET; - if (mReq->req.complete != NULL) { - spin_unlock(mEp->lock); - mReq->req.complete(&mEp->ep, &mReq->req); - spin_lock(mEp->lock); + if (hwreq->req.complete != NULL) { + spin_unlock(hwep->lock); + hwreq->req.complete(&hwep->ep, &hwreq->req); + spin_lock(hwep->lock); } - spin_unlock_irqrestore(mEp->lock, flags); + spin_unlock_irqrestore(hwep->lock, flags); return 0; } @@ -1273,38 +1362,40 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req) */ static int ep_set_halt(struct usb_ep *ep, int value) { - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); int direction, retval = 0; unsigned long flags; - if (ep == NULL || mEp->ep.desc == NULL) + if (ep == NULL || hwep->ep.desc == NULL) return -EINVAL; - spin_lock_irqsave(mEp->lock, flags); + if (usb_endpoint_xfer_isoc(hwep->ep.desc)) + return -EOPNOTSUPP; + + spin_lock_irqsave(hwep->lock, flags); #ifndef STALL_IN /* g_file_storage MS compliant but g_zero fails chapter 9 compliance */ - if (value && mEp->type == USB_ENDPOINT_XFER_BULK && mEp->dir == TX && - !list_empty(&mEp->qh.queue)) { - spin_unlock_irqrestore(mEp->lock, flags); + if (value && hwep->type == USB_ENDPOINT_XFER_BULK && hwep->dir == TX && + !list_empty(&hwep->qh.queue)) { + spin_unlock_irqrestore(hwep->lock, flags); return -EAGAIN; } #endif - direction = mEp->dir; + direction = hwep->dir; do { - dbg_event(_usb_addr(mEp), "HALT", value); - retval |= hw_ep_set_halt(mEp->ci, mEp->num, mEp->dir, value); + retval |= hw_ep_set_halt(hwep->ci, hwep->num, hwep->dir, value); if (!value) - mEp->wedge = 0; + hwep->wedge = 0; - if (mEp->type == USB_ENDPOINT_XFER_CONTROL) - mEp->dir = (mEp->dir == TX) ? RX : TX; + if (hwep->type == USB_ENDPOINT_XFER_CONTROL) + hwep->dir = (hwep->dir == TX) ? RX : TX; - } while (mEp->dir != direction); + } while (hwep->dir != direction); - spin_unlock_irqrestore(mEp->lock, flags); + spin_unlock_irqrestore(hwep->lock, flags); return retval; } @@ -1315,18 +1406,15 @@ static int ep_set_halt(struct usb_ep *ep, int value) */ static int ep_set_wedge(struct usb_ep *ep) { - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); unsigned long flags; - if (ep == NULL || mEp->ep.desc == NULL) + if (ep == NULL || hwep->ep.desc == NULL) return -EINVAL; - spin_lock_irqsave(mEp->lock, flags); - - dbg_event(_usb_addr(mEp), "WEDGE", 0); - mEp->wedge = 1; - - spin_unlock_irqrestore(mEp->lock, flags); + spin_lock_irqsave(hwep->lock, flags); + hwep->wedge = 1; + spin_unlock_irqrestore(hwep->lock, flags); return usb_ep_set_halt(ep); } @@ -1338,20 +1426,19 @@ static int ep_set_wedge(struct usb_ep *ep) */ static void ep_fifo_flush(struct usb_ep *ep) { - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); unsigned long flags; if (ep == NULL) { - dev_err(mEp->ci->dev, "%02X: -EINVAL\n", _usb_addr(mEp)); + dev_err(hwep->ci->dev, "%02X: -EINVAL\n", _usb_addr(hwep)); return; } - spin_lock_irqsave(mEp->lock, flags); + spin_lock_irqsave(hwep->lock, flags); - dbg_event(_usb_addr(mEp), "FFLUSH", 0); - hw_ep_flush(mEp->ci, mEp->num, mEp->dir); + hw_ep_flush(hwep->ci, hwep->num, hwep->dir); - spin_unlock_irqrestore(mEp->lock, flags); + spin_unlock_irqrestore(hwep->lock, flags); } /** @@ -1373,15 +1460,12 @@ static const struct usb_ep_ops usb_ep_ops = { /****************************************************************************** * GADGET block *****************************************************************************/ -static int ci13xxx_vbus_session(struct usb_gadget *_gadget, int is_active) +static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active) { - struct ci13xxx *ci = container_of(_gadget, struct ci13xxx, gadget); + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); unsigned long flags; int gadget_ready = 0; - if (!(ci->platdata->flags & CI13XXX_PULLUP_ON_VBUS)) - return -EOPNOTSUPP; - spin_lock_irqsave(&ci->lock, flags); ci->vbus_active = is_active; if (ci->driver) @@ -1392,24 +1476,27 @@ static int ci13xxx_vbus_session(struct usb_gadget *_gadget, int is_active) if (is_active) { pm_runtime_get_sync(&_gadget->dev); hw_device_reset(ci, USBMODE_CM_DC); - hw_enable_vbus_intr(ci); hw_device_state(ci, ci->ep0out->qh.dma); + usb_gadget_set_state(_gadget, USB_STATE_POWERED); } else { + if (ci->driver) + ci->driver->disconnect(&ci->gadget); hw_device_state(ci, 0); if (ci->platdata->notify_event) ci->platdata->notify_event(ci, - CI13XXX_CONTROLLER_STOPPED_EVENT); + CI_HDRC_CONTROLLER_STOPPED_EVENT); _gadget_stop_activity(&ci->gadget); pm_runtime_put_sync(&_gadget->dev); + usb_gadget_set_state(_gadget, USB_STATE_NOTATTACHED); } } return 0; } -static int ci13xxx_wakeup(struct usb_gadget *_gadget) +static int ci_udc_wakeup(struct usb_gadget *_gadget) { - struct ci13xxx *ci = container_of(_gadget, struct ci13xxx, gadget); + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); unsigned long flags; int ret = 0; @@ -1428,21 +1515,24 @@ out: return ret; } -static int ci13xxx_vbus_draw(struct usb_gadget *_gadget, unsigned mA) +static int ci_udc_vbus_draw(struct usb_gadget *_gadget, unsigned ma) { - struct ci13xxx *ci = container_of(_gadget, struct ci13xxx, gadget); + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); if (ci->transceiver) - return usb_phy_set_power(ci->transceiver, mA); + return usb_phy_set_power(ci->transceiver, ma); return -ENOTSUPP; } /* Change Data+ pullup status * this func is used by usb_gadget_connect/disconnet */ -static int ci13xxx_pullup(struct usb_gadget *_gadget, int is_on) +static int ci_udc_pullup(struct usb_gadget *_gadget, int is_on) { - struct ci13xxx *ci = container_of(_gadget, struct ci13xxx, gadget); + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + + if (!ci->vbus_active) + return -EOPNOTSUPP; if (is_on) hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); @@ -1452,9 +1542,9 @@ static int ci13xxx_pullup(struct usb_gadget *_gadget, int is_on) return 0; } -static int ci13xxx_start(struct usb_gadget *gadget, +static int ci_udc_start(struct usb_gadget *gadget, struct usb_gadget_driver *driver); -static int ci13xxx_stop(struct usb_gadget *gadget, +static int ci_udc_stop(struct usb_gadget *gadget, struct usb_gadget_driver *driver); /** * Device operations part of the API to the USB controller hardware, @@ -1462,46 +1552,46 @@ static int ci13xxx_stop(struct usb_gadget *gadget, * Check "usb_gadget.h" for details */ static const struct usb_gadget_ops usb_gadget_ops = { - .vbus_session = ci13xxx_vbus_session, - .wakeup = ci13xxx_wakeup, - .pullup = ci13xxx_pullup, - .vbus_draw = ci13xxx_vbus_draw, - .udc_start = ci13xxx_start, - .udc_stop = ci13xxx_stop, + .vbus_session = ci_udc_vbus_session, + .wakeup = ci_udc_wakeup, + .pullup = ci_udc_pullup, + .vbus_draw = ci_udc_vbus_draw, + .udc_start = ci_udc_start, + .udc_stop = ci_udc_stop, }; -static int init_eps(struct ci13xxx *ci) +static int init_eps(struct ci_hdrc *ci) { int retval = 0, i, j; for (i = 0; i < ci->hw_ep_max/2; i++) for (j = RX; j <= TX; j++) { int k = i + j * ci->hw_ep_max/2; - struct ci13xxx_ep *mEp = &ci->ci13xxx_ep[k]; + struct ci_hw_ep *hwep = &ci->ci_hw_ep[k]; - scnprintf(mEp->name, sizeof(mEp->name), "ep%i%s", i, + scnprintf(hwep->name, sizeof(hwep->name), "ep%i%s", i, (j == TX) ? "in" : "out"); - mEp->ci = ci; - mEp->lock = &ci->lock; - mEp->td_pool = ci->td_pool; + hwep->ci = ci; + hwep->lock = &ci->lock; + hwep->td_pool = ci->td_pool; - mEp->ep.name = mEp->name; - mEp->ep.ops = &usb_ep_ops; + hwep->ep.name = hwep->name; + hwep->ep.ops = &usb_ep_ops; /* * for ep0: maxP defined in desc, for other * eps, maxP is set by epautoconfig() called * by gadget layer */ - mEp->ep.maxpacket = (unsigned short)~0; + usb_ep_set_maxpacket_limit(&hwep->ep, (unsigned short)~0); - INIT_LIST_HEAD(&mEp->qh.queue); - mEp->qh.ptr = dma_pool_alloc(ci->qh_pool, GFP_KERNEL, - &mEp->qh.dma); - if (mEp->qh.ptr == NULL) + INIT_LIST_HEAD(&hwep->qh.queue); + hwep->qh.ptr = dma_pool_alloc(ci->qh_pool, GFP_KERNEL, + &hwep->qh.dma); + if (hwep->qh.ptr == NULL) retval = -ENOMEM; else - memset(mEp->qh.ptr, 0, sizeof(*mEp->qh.ptr)); + memset(hwep->qh.ptr, 0, sizeof(*hwep->qh.ptr)); /* * set up shorthands for ep0 out and in endpoints, @@ -1509,42 +1599,44 @@ static int init_eps(struct ci13xxx *ci) */ if (i == 0) { if (j == RX) - ci->ep0out = mEp; + ci->ep0out = hwep; else - ci->ep0in = mEp; + ci->ep0in = hwep; - mEp->ep.maxpacket = CTRL_PAYLOAD_MAX; + usb_ep_set_maxpacket_limit(&hwep->ep, CTRL_PAYLOAD_MAX); continue; } - list_add_tail(&mEp->ep.ep_list, &ci->gadget.ep_list); + list_add_tail(&hwep->ep.ep_list, &ci->gadget.ep_list); } return retval; } -static void destroy_eps(struct ci13xxx *ci) +static void destroy_eps(struct ci_hdrc *ci) { int i; for (i = 0; i < ci->hw_ep_max; i++) { - struct ci13xxx_ep *mEp = &ci->ci13xxx_ep[i]; + struct ci_hw_ep *hwep = &ci->ci_hw_ep[i]; - dma_pool_free(ci->qh_pool, mEp->qh.ptr, mEp->qh.dma); + if (hwep->pending_td) + free_pending_td(hwep); + dma_pool_free(ci->qh_pool, hwep->qh.ptr, hwep->qh.dma); } } /** - * ci13xxx_start: register a gadget driver + * ci_udc_start: register a gadget driver * @gadget: our gadget * @driver: the driver being registered * * Interrupts are enabled here. */ -static int ci13xxx_start(struct usb_gadget *gadget, +static int ci_udc_start(struct usb_gadget *gadget, struct usb_gadget_driver *driver) { - struct ci13xxx *ci = container_of(gadget, struct ci13xxx, gadget); + struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); unsigned long flags; int retval = -ENOMEM; @@ -1561,55 +1653,55 @@ static int ci13xxx_start(struct usb_gadget *gadget, retval = usb_ep_enable(&ci->ep0in->ep); if (retval) return retval; - spin_lock_irqsave(&ci->lock, flags); ci->driver = driver; + + /* Start otg fsm for B-device */ + if (ci_otg_is_fsm_mode(ci) && ci->fsm.id) { + ci_hdrc_otg_fsm_start(ci); + return retval; + } + pm_runtime_get_sync(&ci->gadget.dev); - if (ci->platdata->flags & CI13XXX_PULLUP_ON_VBUS) { - if (ci->vbus_active) { - if (ci->platdata->flags & CI13XXX_REGS_SHARED) { - hw_device_reset(ci, USBMODE_CM_DC); - hw_enable_vbus_intr(ci); - } - } else { - pm_runtime_put_sync(&ci->gadget.dev); - goto done; - } + if (ci->vbus_active) { + spin_lock_irqsave(&ci->lock, flags); + hw_device_reset(ci, USBMODE_CM_DC); + } else { + pm_runtime_put_sync(&ci->gadget.dev); + return retval; } retval = hw_device_state(ci, ci->ep0out->qh.dma); + spin_unlock_irqrestore(&ci->lock, flags); if (retval) pm_runtime_put_sync(&ci->gadget.dev); - done: - spin_unlock_irqrestore(&ci->lock, flags); return retval; } /** - * ci13xxx_stop: unregister a gadget driver + * ci_udc_stop: unregister a gadget driver */ -static int ci13xxx_stop(struct usb_gadget *gadget, +static int ci_udc_stop(struct usb_gadget *gadget, struct usb_gadget_driver *driver) { - struct ci13xxx *ci = container_of(gadget, struct ci13xxx, gadget); + struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); unsigned long flags; spin_lock_irqsave(&ci->lock, flags); - if (!(ci->platdata->flags & CI13XXX_PULLUP_ON_VBUS) || - ci->vbus_active) { + if (ci->vbus_active) { hw_device_state(ci, 0); if (ci->platdata->notify_event) ci->platdata->notify_event(ci, - CI13XXX_CONTROLLER_STOPPED_EVENT); - ci->driver = NULL; + CI_HDRC_CONTROLLER_STOPPED_EVENT); spin_unlock_irqrestore(&ci->lock, flags); _gadget_stop_activity(&ci->gadget); spin_lock_irqsave(&ci->lock, flags); pm_runtime_put(&ci->gadget.dev); } + ci->driver = NULL; spin_unlock_irqrestore(&ci->lock, flags); return 0; @@ -1624,7 +1716,7 @@ static int ci13xxx_stop(struct usb_gadget *gadget, * This function returns IRQ_HANDLED if the IRQ has been handled * It locks access to registers */ -static irqreturn_t udc_irq(struct ci13xxx *ci) +static irqreturn_t udc_irq(struct ci_hdrc *ci) { irqreturn_t retval; u32 intr; @@ -1634,7 +1726,7 @@ static irqreturn_t udc_irq(struct ci13xxx *ci) spin_lock(&ci->lock); - if (ci->platdata->flags & CI13XXX_REGS_SHARED) { + if (ci->platdata->flags & CI_HDRC_REGS_SHARED) { if (hw_read(ci, OP_USBMODE, USBMODE_CM) != USBMODE_CM_DC) { spin_unlock(&ci->lock); @@ -1642,7 +1734,6 @@ static irqreturn_t udc_irq(struct ci13xxx *ci) } } intr = hw_test_and_clear_intr_active(ci); - dbg_interrupt(intr); if (intr) { /* order defines priority - do NOT change it */ @@ -1669,6 +1760,8 @@ static irqreturn_t udc_irq(struct ci13xxx *ci) ci->suspended = 1; spin_unlock(&ci->lock); ci->driver->suspend(&ci->gadget); + usb_gadget_set_state(&ci->gadget, + USB_STATE_SUSPENDED); spin_lock(&ci->lock); } } @@ -1676,33 +1769,16 @@ static irqreturn_t udc_irq(struct ci13xxx *ci) } else { retval = IRQ_NONE; } - - intr = hw_read(ci, OP_OTGSC, ~0); - hw_write(ci, OP_OTGSC, ~0, intr); - - if (intr & (OTGSC_AVVIE & OTGSC_AVVIS)) - queue_work(ci->wq, &ci->vbus_work); - spin_unlock(&ci->lock); return retval; } /** - * udc_release: driver release function - * @dev: device - * - * Currently does nothing - */ -static void udc_release(struct device *dev) -{ -} - -/** * udc_start: initialize gadget role * @ci: chipidea controller */ -static int udc_start(struct ci13xxx *ci) +static int udc_start(struct ci_hdrc *ci) { struct device *dev = ci->dev; int retval = 0; @@ -1712,27 +1788,21 @@ static int udc_start(struct ci13xxx *ci) ci->gadget.ops = &usb_gadget_ops; ci->gadget.speed = USB_SPEED_UNKNOWN; ci->gadget.max_speed = USB_SPEED_HIGH; - ci->gadget.is_otg = 0; + ci->gadget.is_otg = ci->is_otg ? 1 : 0; ci->gadget.name = ci->platdata->name; INIT_LIST_HEAD(&ci->gadget.ep_list); - dev_set_name(&ci->gadget.dev, "gadget"); - ci->gadget.dev.dma_mask = dev->dma_mask; - ci->gadget.dev.coherent_dma_mask = dev->coherent_dma_mask; - ci->gadget.dev.parent = dev; - ci->gadget.dev.release = udc_release; - /* alloc resources */ - ci->qh_pool = dma_pool_create("ci13xxx_qh", dev, - sizeof(struct ci13xxx_qh), - 64, CI13XXX_PAGE_SIZE); + ci->qh_pool = dma_pool_create("ci_hw_qh", dev, + sizeof(struct ci_hw_qh), + 64, CI_HDRC_PAGE_SIZE); if (ci->qh_pool == NULL) return -ENOMEM; - ci->td_pool = dma_pool_create("ci13xxx_td", dev, - sizeof(struct ci13xxx_td), - 64, CI13XXX_PAGE_SIZE); + ci->td_pool = dma_pool_create("ci_hw_td", dev, + sizeof(struct ci_hw_td), + 64, CI_HDRC_PAGE_SIZE); if (ci->td_pool == NULL) { retval = -ENOMEM; goto free_qh_pool; @@ -1744,64 +1814,15 @@ static int udc_start(struct ci13xxx *ci) ci->gadget.ep0 = &ci->ep0in->ep; - if (ci->global_phy) - ci->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); - - if (ci->platdata->flags & CI13XXX_REQUIRE_TRANSCEIVER) { - if (ci->transceiver == NULL) { - retval = -ENODEV; - goto destroy_eps; - } - } - - if (!(ci->platdata->flags & CI13XXX_REGS_SHARED)) { - retval = hw_device_reset(ci, USBMODE_CM_DC); - if (retval) - goto put_transceiver; - hw_enable_vbus_intr(ci); - } - - retval = device_register(&ci->gadget.dev); - if (retval) { - put_device(&ci->gadget.dev); - goto put_transceiver; - } - - retval = dbg_create_files(&ci->gadget.dev); - if (retval) - goto unreg_device; - - if (!IS_ERR_OR_NULL(ci->transceiver)) { - retval = otg_set_peripheral(ci->transceiver->otg, - &ci->gadget); - if (retval) - goto remove_dbg; - } - retval = usb_add_gadget_udc(dev, &ci->gadget); if (retval) - goto remove_trans; + goto destroy_eps; pm_runtime_no_callbacks(&ci->gadget.dev); pm_runtime_enable(&ci->gadget.dev); return retval; -remove_trans: - if (!IS_ERR_OR_NULL(ci->transceiver)) { - otg_set_peripheral(ci->transceiver->otg, NULL); - if (ci->global_phy) - usb_put_phy(ci->transceiver); - } - - dev_err(dev, "error = %i\n", retval); -remove_dbg: - dbg_remove_files(&ci->gadget.dev); -unreg_device: - device_unregister(&ci->gadget.dev); -put_transceiver: - if (!IS_ERR_OR_NULL(ci->transceiver) && ci->global_phy) - usb_put_phy(ci->transceiver); destroy_eps: destroy_eps(ci); free_pools: @@ -1812,43 +1833,50 @@ free_qh_pool: } /** - * udc_remove: parent remove must call this to remove UDC + * ci_hdrc_gadget_destroy: parent remove must call this to remove UDC * * No interrupts active, the IRQ has been released */ -static void udc_stop(struct ci13xxx *ci) +void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) { - if (ci == NULL) + if (!ci->roles[CI_ROLE_GADGET]) return; - hw_disable_vbus_intr(ci); - cancel_work_sync(&ci->vbus_work); - usb_del_gadget_udc(&ci->gadget); destroy_eps(ci); dma_pool_destroy(ci->td_pool); dma_pool_destroy(ci->qh_pool); +} - if (!IS_ERR_OR_NULL(ci->transceiver)) { - otg_set_peripheral(ci->transceiver->otg, NULL); - if (ci->global_phy) - usb_put_phy(ci->transceiver); - } - dbg_remove_files(&ci->gadget.dev); - device_unregister(&ci->gadget.dev); - /* my kobject is dynamic, I swear! */ - memset(&ci->gadget, 0, sizeof(ci->gadget)); +static int udc_id_switch_for_device(struct ci_hdrc *ci) +{ + if (ci->is_otg) + /* Clear and enable BSV irq */ + hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE, + OTGSC_BSVIS | OTGSC_BSVIE); + + return 0; +} + +static void udc_id_switch_for_host(struct ci_hdrc *ci) +{ + /* + * host doesn't care B_SESSION_VALID event + * so clear and disbale BSV irq + */ + if (ci->is_otg) + hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS); } /** * ci_hdrc_gadget_init - initialize device related bits * ci: the controller * - * This function enables the gadget role, if the device is "device capable". + * This function initializes the gadget, if the device is "device capable". */ -int ci_hdrc_gadget_init(struct ci13xxx *ci) +int ci_hdrc_gadget_init(struct ci_hdrc *ci) { struct ci_role_driver *rdrv; @@ -1859,12 +1887,11 @@ int ci_hdrc_gadget_init(struct ci13xxx *ci) if (!rdrv) return -ENOMEM; - rdrv->start = udc_start; - rdrv->stop = udc_stop; + rdrv->start = udc_id_switch_for_device; + rdrv->stop = udc_id_switch_for_host; rdrv->irq = udc_irq; rdrv->name = "gadget"; ci->roles[CI_ROLE_GADGET] = rdrv; - INIT_WORK(&ci->vbus_work, vbus_work); - return 0; + return udc_start(ci); } diff --git a/drivers/usb/chipidea/udc.h b/drivers/usb/chipidea/udc.h index 4ff2384d7ca..e66df0020bd 100644 --- a/drivers/usb/chipidea/udc.h +++ b/drivers/usb/chipidea/udc.h @@ -20,7 +20,7 @@ #define TX 1 /* similar to USB_DIR_IN but can be used as an index */ /* DMA layout of transfer descriptors */ -struct ci13xxx_td { +struct ci_hw_td { /* 0 */ u32 next; #define TD_TERMINATE BIT(0) @@ -40,27 +40,34 @@ struct ci13xxx_td { #define TD_CURR_OFFSET (0x0FFFUL << 0) #define TD_FRAME_NUM (0x07FFUL << 0) #define TD_RESERVED_MASK (0x0FFFUL << 0) -} __attribute__ ((packed)); +} __attribute__ ((packed, aligned(4))); /* DMA layout of queue heads */ -struct ci13xxx_qh { +struct ci_hw_qh { /* 0 */ u32 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 */ u32 curr; /* 2 - 8 */ - struct ci13xxx_td td; + struct ci_hw_td td; /* 9 */ u32 RESERVED; struct usb_ctrlrequest setup; -} __attribute__ ((packed)); +} __attribute__ ((packed, aligned(4))); + +struct td_node { + struct list_head td; + dma_addr_t dma; + struct ci_hw_td *ptr; +}; /** - * struct ci13xxx_req - usb request representation + * struct ci_hw_req - usb request representation * @req: request structure for gadget drivers * @queue: link to QH list * @ptr: transfer descriptor for this request @@ -68,26 +75,29 @@ struct ci13xxx_qh { * @zptr: transfer descriptor for the zero packet * @zdma: dma address of the zero packet's transfer descriptor */ -struct ci13xxx_req { +struct ci_hw_req { struct usb_request req; struct list_head queue; - struct ci13xxx_td *ptr; - dma_addr_t dma; - struct ci13xxx_td *zptr; - dma_addr_t zdma; + struct list_head tds; }; #ifdef CONFIG_USB_CHIPIDEA_UDC -int ci_hdrc_gadget_init(struct ci13xxx *ci); +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 ci13xxx *ci) +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 */ diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c new file mode 100644 index 00000000000..85293b8b1df --- /dev/null +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -0,0 +1,355 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/delay.h> + +#include "ci_hdrc_imx.h" + +#define MX25_USB_PHY_CTRL_OFFSET 0x08 +#define MX25_BM_EXTERNAL_VBUS_DIVIDER BIT(23) + +#define MX25_EHCI_INTERFACE_SINGLE_UNI (2 << 0) +#define MX25_EHCI_INTERFACE_DIFF_UNI (0 << 0) +#define MX25_EHCI_INTERFACE_MASK (0xf) + +#define MX25_OTG_SIC_SHIFT 29 +#define MX25_OTG_SIC_MASK (0x3 << MX25_OTG_SIC_SHIFT) +#define MX25_OTG_PM_BIT BIT(24) +#define MX25_OTG_PP_BIT BIT(11) +#define MX25_OTG_OCPOL_BIT BIT(3) + +#define MX25_H1_SIC_SHIFT 21 +#define MX25_H1_SIC_MASK (0x3 << MX25_H1_SIC_SHIFT) +#define MX25_H1_PP_BIT BIT(18) +#define MX25_H1_PM_BIT BIT(16) +#define MX25_H1_IPPUE_UP_BIT BIT(7) +#define MX25_H1_IPPUE_DOWN_BIT BIT(6) +#define MX25_H1_TLL_BIT BIT(5) +#define MX25_H1_USBTE_BIT BIT(4) +#define MX25_H1_OCPOL_BIT BIT(2) + +#define MX27_H1_PM_BIT BIT(8) +#define MX27_H2_PM_BIT BIT(16) +#define MX27_OTG_PM_BIT BIT(24) + +#define MX53_USB_OTG_PHY_CTRL_0_OFFSET 0x08 +#define MX53_USB_OTG_PHY_CTRL_1_OFFSET 0x0c +#define MX53_USB_UH2_CTRL_OFFSET 0x14 +#define MX53_USB_UH3_CTRL_OFFSET 0x18 +#define MX53_BM_OVER_CUR_DIS_H1 BIT(5) +#define MX53_BM_OVER_CUR_DIS_OTG BIT(8) +#define MX53_BM_OVER_CUR_DIS_UHx BIT(30) +#define MX53_USB_PHYCTRL1_PLLDIV_MASK 0x3 +#define MX53_USB_PLL_DIV_24_MHZ 0x01 + +#define MX6_BM_OVER_CUR_DIS BIT(7) + +struct usbmisc_ops { + /* It's called once when probe a usb device */ + int (*init)(struct imx_usbmisc_data *data); + /* It's called once after adding a usb device */ + int (*post)(struct imx_usbmisc_data *data); +}; + +struct imx_usbmisc { + void __iomem *base; + spinlock_t lock; + struct clk *clk; + const struct usbmisc_ops *ops; +}; + +static struct imx_usbmisc *usbmisc; + +static int usbmisc_imx25_init(struct imx_usbmisc_data *data) +{ + unsigned long flags; + u32 val = 0; + + if (data->index > 1) + return -EINVAL; + + spin_lock_irqsave(&usbmisc->lock, flags); + switch (data->index) { + case 0: + val = readl(usbmisc->base); + val &= ~(MX25_OTG_SIC_MASK | MX25_OTG_PP_BIT); + val |= (MX25_EHCI_INTERFACE_DIFF_UNI & MX25_EHCI_INTERFACE_MASK) << MX25_OTG_SIC_SHIFT; + val |= (MX25_OTG_PM_BIT | MX25_OTG_OCPOL_BIT); + writel(val, usbmisc->base); + break; + case 1: + val = readl(usbmisc->base); + val &= ~(MX25_H1_SIC_MASK | MX25_H1_PP_BIT | MX25_H1_IPPUE_UP_BIT); + val |= (MX25_EHCI_INTERFACE_SINGLE_UNI & MX25_EHCI_INTERFACE_MASK) << MX25_H1_SIC_SHIFT; + val |= (MX25_H1_PM_BIT | MX25_H1_OCPOL_BIT | MX25_H1_TLL_BIT | + MX25_H1_USBTE_BIT | MX25_H1_IPPUE_DOWN_BIT); + + writel(val, usbmisc->base); + + break; + } + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + +static int usbmisc_imx25_post(struct imx_usbmisc_data *data) +{ + void __iomem *reg; + unsigned long flags; + u32 val; + + if (data->index > 2) + return -EINVAL; + + reg = usbmisc->base + MX25_USB_PHY_CTRL_OFFSET; + + if (data->evdo) { + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(reg); + writel(val | MX25_BM_EXTERNAL_VBUS_DIVIDER, reg); + spin_unlock_irqrestore(&usbmisc->lock, flags); + usleep_range(5000, 10000); /* needed to stabilize voltage */ + } + + return 0; +} + +static int usbmisc_imx27_init(struct imx_usbmisc_data *data) +{ + unsigned long flags; + u32 val; + + switch (data->index) { + case 0: + val = MX27_OTG_PM_BIT; + break; + case 1: + val = MX27_H1_PM_BIT; + break; + case 2: + val = MX27_H2_PM_BIT; + break; + default: + return -EINVAL; + }; + + spin_lock_irqsave(&usbmisc->lock, flags); + if (data->disable_oc) + val = readl(usbmisc->base) | val; + else + val = readl(usbmisc->base) & ~val; + writel(val, usbmisc->base); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + +static int usbmisc_imx53_init(struct imx_usbmisc_data *data) +{ + void __iomem *reg = NULL; + unsigned long flags; + u32 val = 0; + + if (data->index > 3) + return -EINVAL; + + /* Select a 24 MHz reference clock for the PHY */ + reg = usbmisc->base + MX53_USB_OTG_PHY_CTRL_1_OFFSET; + val = readl(reg); + val &= ~MX53_USB_PHYCTRL1_PLLDIV_MASK; + val |= MX53_USB_PLL_DIV_24_MHZ; + writel(val, usbmisc->base + MX53_USB_OTG_PHY_CTRL_1_OFFSET); + + if (data->disable_oc) { + spin_lock_irqsave(&usbmisc->lock, flags); + switch (data->index) { + case 0: + reg = usbmisc->base + MX53_USB_OTG_PHY_CTRL_0_OFFSET; + val = readl(reg) | MX53_BM_OVER_CUR_DIS_OTG; + break; + case 1: + reg = usbmisc->base + MX53_USB_OTG_PHY_CTRL_0_OFFSET; + val = readl(reg) | MX53_BM_OVER_CUR_DIS_H1; + break; + case 2: + reg = usbmisc->base + MX53_USB_UH2_CTRL_OFFSET; + val = readl(reg) | MX53_BM_OVER_CUR_DIS_UHx; + break; + case 3: + reg = usbmisc->base + MX53_USB_UH3_CTRL_OFFSET; + val = readl(reg) | MX53_BM_OVER_CUR_DIS_UHx; + break; + } + if (reg && val) + writel(val, reg); + spin_unlock_irqrestore(&usbmisc->lock, flags); + } + + return 0; +} + +static int usbmisc_imx6q_init(struct imx_usbmisc_data *data) +{ + unsigned long flags; + u32 reg; + + if (data->index > 3) + return -EINVAL; + + if (data->disable_oc) { + spin_lock_irqsave(&usbmisc->lock, flags); + reg = readl(usbmisc->base + data->index * 4); + writel(reg | MX6_BM_OVER_CUR_DIS, + usbmisc->base + data->index * 4); + spin_unlock_irqrestore(&usbmisc->lock, flags); + } + + return 0; +} + +static const struct usbmisc_ops imx25_usbmisc_ops = { + .init = usbmisc_imx25_init, + .post = usbmisc_imx25_post, +}; + +static const struct usbmisc_ops imx27_usbmisc_ops = { + .init = usbmisc_imx27_init, +}; + +static const struct usbmisc_ops imx53_usbmisc_ops = { + .init = usbmisc_imx53_init, +}; + +static const struct usbmisc_ops imx6q_usbmisc_ops = { + .init = usbmisc_imx6q_init, +}; + +int imx_usbmisc_init(struct imx_usbmisc_data *data) +{ + if (!usbmisc) + return -EPROBE_DEFER; + if (!usbmisc->ops->init) + return 0; + return usbmisc->ops->init(data); +} +EXPORT_SYMBOL_GPL(imx_usbmisc_init); + +int imx_usbmisc_init_post(struct imx_usbmisc_data *data) +{ + if (!usbmisc) + return -EPROBE_DEFER; + if (!usbmisc->ops->post) + return 0; + return usbmisc->ops->post(data); +} +EXPORT_SYMBOL_GPL(imx_usbmisc_init_post); + +static const struct of_device_id usbmisc_imx_dt_ids[] = { + { + .compatible = "fsl,imx25-usbmisc", + .data = &imx25_usbmisc_ops, + }, + { + .compatible = "fsl,imx35-usbmisc", + .data = &imx25_usbmisc_ops, + }, + { + .compatible = "fsl,imx27-usbmisc", + .data = &imx27_usbmisc_ops, + }, + { + .compatible = "fsl,imx51-usbmisc", + .data = &imx53_usbmisc_ops, + }, + { + .compatible = "fsl,imx53-usbmisc", + .data = &imx53_usbmisc_ops, + }, + { + .compatible = "fsl,imx6q-usbmisc", + .data = &imx6q_usbmisc_ops, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, usbmisc_imx_dt_ids); + +static int usbmisc_imx_probe(struct platform_device *pdev) +{ + struct resource *res; + struct imx_usbmisc *data; + int ret; + struct of_device_id *tmp_dev; + + if (usbmisc) + return -EBUSY; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + spin_lock_init(&data->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + data->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(data->clk)) { + dev_err(&pdev->dev, + "failed to get clock, err=%ld\n", PTR_ERR(data->clk)); + return PTR_ERR(data->clk); + } + + ret = clk_prepare_enable(data->clk); + if (ret) { + dev_err(&pdev->dev, + "clk_prepare_enable failed, err=%d\n", ret); + return ret; + } + + tmp_dev = (struct of_device_id *) + of_match_device(usbmisc_imx_dt_ids, &pdev->dev); + data->ops = (const struct usbmisc_ops *)tmp_dev->data; + usbmisc = data; + + return 0; +} + +static int usbmisc_imx_remove(struct platform_device *pdev) +{ + clk_disable_unprepare(usbmisc->clk); + usbmisc = NULL; + return 0; +} + +static struct platform_driver usbmisc_imx_driver = { + .probe = usbmisc_imx_probe, + .remove = usbmisc_imx_remove, + .driver = { + .name = "usbmisc_imx", + .owner = THIS_MODULE, + .of_match_table = usbmisc_imx_dt_ids, + }, +}; + +module_platform_driver(usbmisc_imx_driver); + +MODULE_ALIAS("platform:usbmisc-imx"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("driver for imx usb non-core registers"); +MODULE_AUTHOR("Richard Zhao <richard.zhao@freescale.com>"); diff --git a/drivers/usb/chipidea/usbmisc_imx6q.c b/drivers/usb/chipidea/usbmisc_imx6q.c deleted file mode 100644 index 845efe29e6b..00000000000 --- a/drivers/usb/chipidea/usbmisc_imx6q.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2012 Freescale Semiconductor, Inc. - * - * The code contained herein is licensed under the GNU General Public - * License. You may obtain a copy of the GNU General Public License - * Version 2 or later at the following locations: - * - * http://www.opensource.org/licenses/gpl-license.html - * http://www.gnu.org/copyleft/gpl.html - */ - -#include <linux/module.h> -#include <linux/of_platform.h> -#include <linux/clk.h> -#include <linux/err.h> -#include <linux/io.h> - -#include "ci13xxx_imx.h" - -#define USB_DEV_MAX 4 - -#define BM_OVER_CUR_DIS BIT(7) - -struct imx6q_usbmisc { - void __iomem *base; - spinlock_t lock; - struct clk *clk; - struct usbmisc_usb_device usbdev[USB_DEV_MAX]; -}; - -static struct imx6q_usbmisc *usbmisc; - -static struct usbmisc_usb_device *get_usbdev(struct device *dev) -{ - int i, ret; - - for (i = 0; i < USB_DEV_MAX; i++) { - if (usbmisc->usbdev[i].dev == dev) - return &usbmisc->usbdev[i]; - else if (!usbmisc->usbdev[i].dev) - break; - } - - if (i >= USB_DEV_MAX) - return ERR_PTR(-EBUSY); - - ret = usbmisc_get_init_data(dev, &usbmisc->usbdev[i]); - if (ret) - return ERR_PTR(ret); - - return &usbmisc->usbdev[i]; -} - -static int usbmisc_imx6q_init(struct device *dev) -{ - - struct usbmisc_usb_device *usbdev; - unsigned long flags; - u32 reg; - - usbdev = get_usbdev(dev); - if (IS_ERR(usbdev)) - return PTR_ERR(usbdev); - - if (usbdev->disable_oc) { - spin_lock_irqsave(&usbmisc->lock, flags); - reg = readl(usbmisc->base + usbdev->index * 4); - writel(reg | BM_OVER_CUR_DIS, - usbmisc->base + usbdev->index * 4); - spin_unlock_irqrestore(&usbmisc->lock, flags); - } - - return 0; -} - -static const struct usbmisc_ops imx6q_usbmisc_ops = { - .init = usbmisc_imx6q_init, -}; - -static const struct of_device_id usbmisc_imx6q_dt_ids[] = { - { .compatible = "fsl,imx6q-usbmisc"}, - { /* sentinel */ } -}; - -static int usbmisc_imx6q_probe(struct platform_device *pdev) -{ - struct resource *res; - struct imx6q_usbmisc *data; - int ret; - - if (usbmisc) - return -EBUSY; - - data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - spin_lock_init(&data->lock); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - data->base = devm_request_and_ioremap(&pdev->dev, res); - if (!data->base) - return -EADDRNOTAVAIL; - - data->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(data->clk)) { - dev_err(&pdev->dev, - "failed to get clock, err=%ld\n", PTR_ERR(data->clk)); - return PTR_ERR(data->clk); - } - - ret = clk_prepare_enable(data->clk); - if (ret) { - dev_err(&pdev->dev, - "clk_prepare_enable failed, err=%d\n", ret); - return ret; - } - - ret = usbmisc_set_ops(&imx6q_usbmisc_ops); - if (ret) { - clk_disable_unprepare(data->clk); - return ret; - } - - usbmisc = data; - - return 0; -} - -static int usbmisc_imx6q_remove(struct platform_device *pdev) -{ - usbmisc_unset_ops(&imx6q_usbmisc_ops); - clk_disable_unprepare(usbmisc->clk); - return 0; -} - -static struct platform_driver usbmisc_imx6q_driver = { - .probe = usbmisc_imx6q_probe, - .remove = usbmisc_imx6q_remove, - .driver = { - .name = "usbmisc_imx6q", - .owner = THIS_MODULE, - .of_match_table = usbmisc_imx6q_dt_ids, - }, -}; - -int __init usbmisc_imx6q_drv_init(void) -{ - return platform_driver_register(&usbmisc_imx6q_driver); -} -subsys_initcall(usbmisc_imx6q_drv_init); - -void __exit usbmisc_imx6q_drv_exit(void) -{ - platform_driver_unregister(&usbmisc_imx6q_driver); -} -module_exit(usbmisc_imx6q_drv_exit); - -MODULE_ALIAS("platform:usbmisc-imx6q"); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("driver for imx6q usb non-core registers"); -MODULE_AUTHOR("Richard Zhao <richard.zhao@freescale.com>"); |
