Commit e443b333 authored by Alexander Shishkin's avatar Alexander Shishkin Committed by Greg Kroah-Hartman

usb: chipidea: split the driver code into units

Split the driver into the following parts:
  * core  -- resources, register access, capabilities, etc;
  * udc   -- device controller functionality;
  * debug -- logging events.
Signed-off-by: default avatarAlexander Shishkin <alexander.shishkin@linux.intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent bc25a80d
config USB_CHIPIDEA
tristate "ChipIdea Highspeed Dual Role Controller"
depends on USB && USB_GADGET
select USB_GADGET_DUALSPEED
depends on USB
help
Say Y here if your system has a dual role high speed USB
controller based on ChipIdea silicon IP. Currently, only the
peripheral mode is supported.
When compiled dynamically, the module will be called ci-hdrc.ko.
if USB_CHIPIDEA
config USB_CHIPIDEA_UDC
bool "ChipIdea device controller"
depends on USB_GADGET
select USB_GADGET_DUALSPEED
help
Say Y here to enable device controller functionality of the
ChipIdea driver.
config USB_CHIPIDEA_DEBUG
bool "ChipIdea driver debug"
help
Say Y here to enable debugging output of the ChipIdea driver.
endif
obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc.o
ci_hdrc-y := ci13xxx_udc.o
ci_hdrc-y := core.o
ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC) += udc.o
ci_hdrc-$(CONFIG_USB_CHIPIDEA_DEBUG) += debug.o
ifneq ($(CONFIG_PCI),)
obj-$(CONFIG_USB_CHIPIDEA) += ci13xxx_pci.o
......
/*
* bits.h - register bits of the ChipIdea USB IP core
*
* Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved.
*
* Author: David Lopo
*
* 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_BITS_H
#define __DRIVERS_USB_CHIPIDEA_BITS_H
/* HCCPARAMS */
#define HCCPARAMS_LEN BIT(17)
/* DCCPARAMS */
#define DCCPARAMS_DEN (0x1F << 0)
#define DCCPARAMS_DC BIT(7)
/* TESTMODE */
#define TESTMODE_FORCE BIT(0)
/* USBCMD */
#define USBCMD_RS BIT(0)
#define USBCMD_RST BIT(1)
#define USBCMD_SUTW BIT(13)
#define USBCMD_ATDTW BIT(14)
/* USBSTS & USBINTR */
#define USBi_UI BIT(0)
#define USBi_UEI BIT(1)
#define USBi_PCI BIT(2)
#define USBi_URI BIT(6)
#define USBi_SLI BIT(8)
/* DEVICEADDR */
#define DEVICEADDR_USBADRA BIT(24)
#define DEVICEADDR_USBADR (0x7FUL << 25)
/* PORTSC */
#define PORTSC_FPR BIT(6)
#define PORTSC_SUSP BIT(7)
#define PORTSC_HSP BIT(9)
#define PORTSC_PTC (0x0FUL << 16)
/* DEVLC */
#define DEVLC_PSPD (0x03UL << 25)
#define DEVLC_PSPD_HS (0x02UL << 25)
/* USBMODE */
#define USBMODE_CM (0x03UL << 0)
#define USBMODE_CM_IDLE (0x00UL << 0)
#define USBMODE_CM_DEVICE (0x02UL << 0)
#define USBMODE_CM_HOST (0x03UL << 0)
#define USBMODE_SLOM BIT(3)
#define USBMODE_SDIS BIT(4)
/* ENDPTCTRL */
#define ENDPTCTRL_RXS BIT(0)
#define ENDPTCTRL_RXT (0x03UL << 2)
#define ENDPTCTRL_RXR BIT(6) /* reserved for port 0 */
#define ENDPTCTRL_RXE BIT(7)
#define ENDPTCTRL_TXS BIT(16)
#define ENDPTCTRL_TXT (0x03UL << 18)
#define ENDPTCTRL_TXR BIT(22) /* reserved for port 0 */
#define ENDPTCTRL_TXE BIT(23)
#endif /* __DRIVERS_USB_CHIPIDEA_BITS_H */
/*
* ci13xxx_udc.h - structures, registers, and macros MIPS USB IP core
* ci.h - common structures, functions, and macros of the ChipIdea driver
*
* Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved.
*
......@@ -8,77 +8,24 @@
* 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.
*
* Description: MIPS USB IP core family device controller
* Structures, registers and logging macros
*/
#ifndef _CI13XXX_h_
#define _CI13XXX_h_
#ifndef __DRIVERS_USB_CHIPIDEA_CI_H
#define __DRIVERS_USB_CHIPIDEA_CI_H
#include <linux/list.h>
#include <linux/usb/gadget.h>
/******************************************************************************
* DEFINE
*****************************************************************************/
#define DMA_ADDR_INVALID (~(dma_addr_t)0)
#define CI13XXX_PAGE_SIZE 4096ul /* page size for TD's */
#define ENDPT_MAX 32
#define CTRL_PAYLOAD_MAX 64
#define RX 0 /* similar to USB_DIR_OUT but can be used as an index */
#define TX 1 /* similar to USB_DIR_IN but can be used as an index */
/******************************************************************************
* STRUCTURES
*****************************************************************************/
/* DMA layout of transfer descriptors */
struct ci13xxx_td {
/* 0 */
u32 next;
#define TD_TERMINATE BIT(0)
#define TD_ADDR_MASK (0xFFFFFFEUL << 5)
/* 1 */
u32 token;
#define TD_STATUS (0x00FFUL << 0)
#define TD_STATUS_TR_ERR BIT(3)
#define TD_STATUS_DT_ERR BIT(5)
#define TD_STATUS_HALTED BIT(6)
#define TD_STATUS_ACTIVE BIT(7)
#define TD_MULTO (0x0003UL << 10)
#define TD_IOC BIT(15)
#define TD_TOTAL_BYTES (0x7FFFUL << 16)
/* 2 */
u32 page[5];
#define TD_CURR_OFFSET (0x0FFFUL << 0)
#define TD_FRAME_NUM (0x07FFUL << 0)
#define TD_RESERVED_MASK (0x0FFFUL << 0)
} __attribute__ ((packed));
/* DMA layout of queue heads */
struct ci13xxx_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)
/* 1 */
u32 curr;
/* 2 - 8 */
struct ci13xxx_td td;
/* 9 */
u32 RESERVED;
struct usb_ctrlrequest setup;
} __attribute__ ((packed));
/* Extension of usb_request */
struct ci13xxx_req {
struct usb_request req;
unsigned map;
struct list_head queue;
struct ci13xxx_td *ptr;
dma_addr_t dma;
struct ci13xxx_td *zptr;
dma_addr_t zdma;
};
/* Extension of usb_ep */
struct ci13xxx_ep {
struct usb_ep ep;
......@@ -100,22 +47,6 @@ struct ci13xxx_ep {
struct dma_pool *td_pool;
};
struct ci13xxx;
struct ci13xxx_udc_driver {
const char *name;
/* offset of the capability registers */
uintptr_t capoffset;
unsigned long flags;
#define CI13XXX_REGS_SHARED BIT(0)
#define CI13XXX_REQUIRE_TRANSCEIVER BIT(1)
#define CI13XXX_PULLUP_ON_VBUS BIT(2)
#define CI13XXX_DISABLE_STREAMING BIT(3)
#define CI13XXX_CONTROLLER_RESET_EVENT 0
#define CI13XXX_CONTROLLER_STOPPED_EVENT 1
void (*notify_event) (struct ci13xxx *udc, unsigned event);
};
struct hw_bank {
unsigned lpm; /* is LPM? */
void __iomem *abs; /* bus map offset */
......@@ -159,9 +90,6 @@ struct ci13xxx {
/******************************************************************************
* REGISTERS
*****************************************************************************/
/* Default offset of capability registers */
#define DEF_CAPOFFSET 0x100
/* register size */
#define REG_BITS (32)
......@@ -190,59 +118,87 @@ enum ci13xxx_regs {
OP_LAST = OP_ENDPTCTRL + ENDPT_MAX / 2,
};
/* HCCPARAMS */
#define HCCPARAMS_LEN BIT(17)
/* DCCPARAMS */
#define DCCPARAMS_DEN (0x1F << 0)
#define DCCPARAMS_DC BIT(7)
/* TESTMODE */
#define TESTMODE_FORCE BIT(0)
/* USBCMD */
#define USBCMD_RS BIT(0)
#define USBCMD_RST BIT(1)
#define USBCMD_SUTW BIT(13)
#define USBCMD_ATDTW BIT(14)
/* USBSTS & USBINTR */
#define USBi_UI BIT(0)
#define USBi_UEI BIT(1)
#define USBi_PCI BIT(2)
#define USBi_URI BIT(6)
#define USBi_SLI BIT(8)
/* DEVICEADDR */
#define DEVICEADDR_USBADRA BIT(24)
#define DEVICEADDR_USBADR (0x7FUL << 25)
/* PORTSC */
#define PORTSC_FPR BIT(6)
#define PORTSC_SUSP BIT(7)
#define PORTSC_HSP BIT(9)
#define PORTSC_PTC (0x0FUL << 16)
/* DEVLC */
#define DEVLC_PSPD (0x03UL << 25)
#define DEVLC_PSPD_HS (0x02UL << 25)
/* USBMODE */
#define USBMODE_CM (0x03UL << 0)
#define USBMODE_CM_IDLE (0x00UL << 0)
#define USBMODE_CM_DEVICE (0x02UL << 0)
#define USBMODE_CM_HOST (0x03UL << 0)
#define USBMODE_SLOM BIT(3)
#define USBMODE_SDIS BIT(4)
/* ENDPTCTRL */
#define ENDPTCTRL_RXS BIT(0)
#define ENDPTCTRL_RXT (0x03UL << 2)
#define ENDPTCTRL_RXR BIT(6) /* reserved for port 0 */
#define ENDPTCTRL_RXE BIT(7)
#define ENDPTCTRL_TXS BIT(16)
#define ENDPTCTRL_TXT (0x03UL << 18)
#define ENDPTCTRL_TXR BIT(22) /* reserved for port 0 */
#define ENDPTCTRL_TXE BIT(23)
#endif /* _CI13XXX_h_ */
/**
* 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
* @mask: bitfield mask
*
* This function returns register contents
*/
static inline u32 hw_read(struct ci13xxx *udc, enum ci13xxx_regs reg, u32 mask)
{
return ioread32(udc->hw_bank.regmap[reg]) & mask;
}
/**
* hw_write: writes to a hw register
* @reg: register index
* @mask: bitfield mask
* @data: new value
*/
static inline void hw_write(struct ci13xxx *udc, enum ci13xxx_regs reg,
u32 mask, u32 data)
{
if (~mask)
data = (ioread32(udc->hw_bank.regmap[reg]) & ~mask)
| (data & mask);
iowrite32(data, udc->hw_bank.regmap[reg]);
}
/**
* hw_test_and_clear: tests & clears a hw register
* @reg: register index
* @mask: bitfield mask
*
* This function returns register contents
*/
static inline u32 hw_test_and_clear(struct ci13xxx *udc, enum ci13xxx_regs reg,
u32 mask)
{
u32 val = ioread32(udc->hw_bank.regmap[reg]) & mask;
iowrite32(val, udc->hw_bank.regmap[reg]);
return val;
}
/**
* hw_test_and_write: tests & writes a hw register
* @reg: register index
* @mask: bitfield mask
* @data: new value
*
* This function returns register contents
*/
static inline u32 hw_test_and_write(struct ci13xxx *udc, enum ci13xxx_regs reg,
u32 mask, u32 data)
{
u32 val = hw_read(udc, reg, ~0);
hw_write(udc, reg, mask, data);
return (val & mask) >> ffs_nr(mask);
}
int hw_device_init(struct ci13xxx *udc, void __iomem *base,
uintptr_t cap_offset);
int hw_device_reset(struct ci13xxx *ci);
int hw_port_test_set(struct ci13xxx *ci, u8 mode);
u8 hw_port_test_get(struct ci13xxx *ci);
#endif /* __DRIVERS_USB_CHIPIDEA_CI_H */
......@@ -11,8 +11,9 @@
#include <linux/usb/msm_hsusb_hw.h>
#include <linux/usb/ulpi.h>
#include <linux/usb/gadget.h>
#include <linux/usb/chipidea.h>
#include "ci13xxx_udc.h"
#include "ci.h"
#define MSM_USB_BASE (udc->regs)
......
......@@ -15,8 +15,7 @@
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/usb/gadget.h>
#include "ci13xxx_udc.h"
#include <linux/usb/chipidea.h>
/* driver name */
#define UDC_DRIVER_NAME "ci13xxx_pci"
......
/*
* core.c - ChipIdea USB IP core family device controller
*
* Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved.
*
* Author: David Lopo
*
* 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.
*/
/*
* Description: ChipIdea USB IP core family device controller
*
* This driver is composed of several blocks:
* - HW: hardware interface
* - DBG: debug facilities (optional)
* - UTIL: utilities
* - ISR: interrupts handling
* - ENDPT: endpoint operations (Gadget API)
* - GADGET: gadget operations (Gadget API)
* - BUS: bus glue code, bus abstraction layer
*
* Compile Options
* - CONFIG_USB_GADGET_DEBUG_FILES: 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
* => case 5: Hi > Di
* => case 8: Hi <> Do
* if undefined usbtest 13 fails
* - TRACE: enable function tracing (depends on DEBUG)
*
* Main Features
* - Chapter 9 & Mass Storage Compliance with Gadget File Storage
* - Chapter 9 Compliance with Gadget Zero (STALL_IN undefined)
* - Normal & LPM support
*
* USBTEST Report
* - OK: 0-12, 13 (STALL_IN defined) & 14
* - 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/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/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/otg.h>
#include <linux/usb/chipidea.h>
#include "ci.h"
#include "udc.h"
#include "bits.h"
#include "debug.h"
/* MSM specific */
#define ABS_AHBBURST (0x0090UL)
#define ABS_AHBMODE (0x0098UL)
/* UDC 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_USBMODE] = 0x068UL,
[OP_ENDPTSETUPSTAT] = 0x06CUL,
[OP_ENDPTPRIME] = 0x070UL,
[OP_ENDPTFLUSH] = 0x074UL,
[OP_ENDPTSTAT] = 0x078UL,
[OP_ENDPTCOMPLETE] = 0x07CUL,
[OP_ENDPTCTRL] = 0x080UL,
};
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_USBMODE] = 0x0C8UL,
[OP_ENDPTSETUPSTAT] = 0x0D8UL,
[OP_ENDPTPRIME] = 0x0DCUL,
[OP_ENDPTFLUSH] = 0x0E0UL,
[OP_ENDPTSTAT] = 0x0E4UL,
[OP_ENDPTCOMPLETE] = 0x0E8UL,
[OP_ENDPTCTRL] = 0x0ECUL,
};
static int hw_alloc_regmap(struct ci13xxx *udc, bool is_lpm)
{
int i;
kfree(udc->hw_bank.regmap);
udc->hw_bank.regmap = kzalloc((OP_LAST + 1) * sizeof(void *),
GFP_KERNEL);
if (!udc->hw_bank.regmap)
return -ENOMEM;
for (i = 0; i < OP_ENDPTCTRL; i++)
udc->hw_bank.regmap[i] =
(i <= CAP_LAST ? udc->hw_bank.cap : udc->hw_bank.op) +
(is_lpm ? ci_regs_lpm[i] : ci_regs_nolpm[i]);
for (; i <= OP_LAST; i++)
udc->hw_bank.regmap[i] = udc->hw_bank.op +
4 * (i - OP_ENDPTCTRL) +
(is_lpm
? ci_regs_lpm[OP_ENDPTCTRL]
: ci_regs_nolpm[OP_ENDPTCTRL]);
return 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)
{
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));
return 0;
}
/**
* hw_port_test_get: reads port test mode value
*
* This function returns port test mode value
*/
u8 hw_port_test_get(struct ci13xxx *ci)
{
return hw_read(ci, OP_PORTSC, PORTSC_PTC) >> ffs_nr(PORTSC_PTC);
}
int hw_device_init(struct ci13xxx *udc, void __iomem *base,
uintptr_t cap_offset)
{
u32 reg;
/* bank is a module variable */
udc->hw_bank.abs = base;
udc->hw_bank.cap = udc->hw_bank.abs;
udc->hw_bank.cap += cap_offset;
udc->hw_bank.op = udc->hw_bank.cap + ioread8(udc->hw_bank.cap);
hw_alloc_regmap(udc, false);
reg = hw_read(udc, CAP_HCCPARAMS, HCCPARAMS_LEN) >>
ffs_nr(HCCPARAMS_LEN);
udc->hw_bank.lpm = reg;
hw_alloc_regmap(udc, !!reg);
udc->hw_bank.size = udc->hw_bank.op - udc->hw_bank.abs;
udc->hw_bank.size += OP_LAST;
udc->hw_bank.size /= sizeof(u32);
reg = hw_read(udc, CAP_DCCPARAMS, DCCPARAMS_DEN) >>
ffs_nr(DCCPARAMS_DEN);
udc->hw_ep_max = reg * 2; /* cache hw ENDPT_MAX */
if (udc->hw_ep_max == 0 || udc->hw_ep_max > ENDPT_MAX)
return -ENODEV;
dev_dbg(udc->dev, "ChipIdea UDC found, lpm: %d; cap: %p op: %p\n",
udc->hw_bank.lpm, udc->hw_bank.cap, udc->hw_bank.op);
/* setup lock mode ? */
/* ENDPTSETUPSTAT is '0' by default */
/* HCSPARAMS.bf.ppc SHOULD BE zero for device */
return 0;
}
/**
* hw_device_reset: resets chip (execute without interruption)
* @ci: the controller
*
* This function returns an error code
*/
int hw_device_reset(struct ci13xxx *ci)
{
/* should flush & stop before reset */
hw_write(ci, OP_ENDPTFLUSH, ~0, ~0);
hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
hw_write(ci, OP_USBCMD, USBCMD_RST, USBCMD_RST);
while (hw_read(ci, OP_USBCMD, USBCMD_RST))
udelay(10); /* not RTOS friendly */
if (ci->udc_driver->notify_event)
ci->udc_driver->notify_event(ci,
CI13XXX_CONTROLLER_RESET_EVENT);
if (ci->udc_driver->flags & CI13XXX_DISABLE_STREAMING)
hw_write(ci, OP_USBMODE, USBMODE_SDIS, USBMODE_SDIS);
/* USBMODE should be configured step by step */
hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE);
hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_DEVICE);
/* HW >= 2.3 */
hw_write(ci, OP_USBMODE, USBMODE_SLOM, USBMODE_SLOM);
if (hw_read(ci, OP_USBMODE, USBMODE_CM) != USBMODE_CM_DEVICE) {
pr_err("cannot enter in device mode");
pr_err("lpm = %i", ci->hw_bank.lpm);
return -ENODEV;
}
return 0;
}
static int __devinit ci_udc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ci13xxx_udc_driver *driver = dev->platform_data;
struct ci13xxx *udc;
struct resource *res;
void __iomem *base;
int ret;
if (!driver) {
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;
}
ret = udc_probe(driver, dev, base, &udc);
if (ret)
return ret;
udc->irq = platform_get_irq(pdev, 0);
if (udc->irq < 0) {
dev_err(dev, "missing IRQ\n");
ret = -ENODEV;
goto out;
}
platform_set_drvdata(pdev, udc);
ret = request_irq(udc->irq, udc_irq, IRQF_SHARED, driver->name, udc);
out:
if (ret)
udc_remove(udc);
return ret;
}
static int __devexit ci_udc_remove(struct platform_device *pdev)
{
struct ci13xxx *udc = platform_get_drvdata(pdev);
free_irq(udc->irq, udc);
udc_remove(udc);
return 0;
}
static struct platform_driver ci_udc_driver = {
.probe = ci_udc_probe,
.remove = __devexit_p(ci_udc_remove),
.driver = {
.name = "ci_udc",
},
};
module_platform_driver(ci_udc_driver);
MODULE_ALIAS("platform:ci_udc");
MODULE_ALIAS("platform:ci13xxx");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("David Lopo <dlopo@chipidea.mips.com>");
MODULE_DESCRIPTION("ChipIdea UDC Driver");
#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/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/otg.h>
#include <linux/usb/chipidea.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++;
}
/**
* hw_register_read: reads all device registers (execute without interruption)
* @buf: destination buffer
* @size: buffer size
*
* This function returns number of registers read
*/
static size_t hw_register_read(struct ci13xxx *udc, u32 *buf, size_t size)
{
unsigned i;
if (size > udc->hw_bank.size)
size = udc->hw_bank.size;
for (i = 0; i < size; i++)
buf[i] = hw_read(udc, i * sizeof(u32), ~0);
return size;
}
/**
* hw_register_write: writes to register
* @addr: register address
* @data: register value
*
* This function returns an error code
*/
static int hw_register_write(struct ci13xxx *udc, u16 addr, u32 data)
{
/* align */
addr /= sizeof(u32);
if (addr >= udc->hw_bank.size)
return -EINVAL;
/* align */
addr *= sizeof(u32);
hw_write(udc, 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
*/
static int hw_intr_clear(struct ci13xxx *udc, int n)
{
if (n >= REG_BITS)
return -EINVAL;
hw_write(udc, OP_USBINTR, BIT(n), 0);
hw_write(udc, OP_USBSTS, BIT(n), BIT(n));
return 0;
}
/**
* 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 *udc, int n)
{
if (n >= REG_BITS)
return -EINVAL;
hw_write(udc, CAP_TESTMODE, TESTMODE_FORCE, TESTMODE_FORCE);
hw_write(udc, OP_USBINTR, BIT(n), BIT(n));
hw_write(udc, OP_USBSTS, BIT(n), BIT(n));
hw_write(udc, CAP_TESTMODE, TESTMODE_FORCE, 0);
return 0;
}
/**
* 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)
{
struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
struct usb_gadget *gadget = &udc->gadget;
int n = 0;
if (attr == NULL || buf == NULL) {
dev_err(udc->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);
/* TODO: Scheduled for removal in 3.8. */
n += scnprintf(buf + n, PAGE_SIZE - n, "is_dualspeed = %d\n",
gadget_is_dualspeed(gadget));
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;
}
static DEVICE_ATTR(device, S_IRUSR, show_device, NULL);
/**
* show_driver: prints information about attached gadget (if any)
*
* Check "device.h" for details
*/
static ssize_t show_driver(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
struct usb_gadget_driver *driver = udc->driver;
int n = 0;
if (attr == NULL || buf == NULL) {
dev_err(dev, "[%s] EINVAL\n", __func__);
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);
return n;
}
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)
{
*idx = (*idx - 1) & (DBG_DATA_MAX-1);
}
/**
* dbg_inc: increments debug event index
* @idx: buffer index
*/
static void dbg_inc(unsigned *idx)
{
*idx = (*idx + 1) & (DBG_DATA_MAX-1);
}
/**
* dbg_print: prints the common part of the event
* @addr: endpoint address
* @name: event name
* @status: status
* @extra: extra information
*/
static void dbg_print(u8 addr, const char *name, int status, const char *extra)
{
struct timeval tval;
unsigned int stamp;
unsigned long flags;
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];
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);
}
/**
* 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, "");
}
/*
* 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)
{
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);
}
}
/**
* 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);
}
}
/**
* 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)
{
unsigned long flags;
unsigned i, j, n = 0;
if (attr == NULL || buf == NULL) {
dev_err(dev->parent, "[%s] EINVAL\n", __func__);
return 0;
}
read_lock_irqsave(&dbg_data.lck, flags);
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]);
read_unlock_irqrestore(&dbg_data.lck, flags);
return n;
}
/**
* 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;
if (attr == NULL || buf == NULL) {
dev_err(dev, "[%s] EINVAL\n", __func__);
goto done;
}
if (sscanf(buf, "%u", &tty) != 1 || tty > 1) {
dev_err(dev, "<1|0>: enable|disable console log\n");
goto done;
}
dbg_data.tty = tty;
dev_info(dev, "tty = %u", dbg_data.tty);
done:
return count;
}
static DEVICE_ATTR(events, S_IRUSR | S_IWUSR, show_events, store_events);
/**
* 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 *udc = container_of(dev, struct ci13xxx, gadget.dev);
unsigned long flags;
u32 intr;
unsigned i, j, n = 0;
if (attr == NULL || buf == NULL) {
dev_err(udc->dev, "[%s] EINVAL\n", __func__);
return 0;
}
spin_lock_irqsave(&udc->lock, flags);
/*n += scnprintf(buf + n, PAGE_SIZE - n,
"status = %08x\n", hw_read_intr_status(udc));
n += scnprintf(buf + n, PAGE_SIZE - n,
"enable = %08x\n", hw_read_intr_enable(udc));*/
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");
}
spin_unlock_irqrestore(&udc->lock, flags);
return n;
}
/**
* 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 *udc = container_of(dev, struct ci13xxx, gadget.dev);
unsigned long flags;
unsigned en, bit;
if (attr == NULL || buf == NULL) {
dev_err(udc->dev, "EINVAL\n");
goto done;
}
if (sscanf(buf, "%u %u", &en, &bit) != 2 || en > 1) {
dev_err(udc->dev, "<1|0> <bit>: enable|disable interrupt\n");
goto done;
}
spin_lock_irqsave(&udc->lock, flags);
if (en) {
if (hw_intr_force(udc, bit))
dev_err(dev, "invalid bit number\n");
else
isr_statistics.test++;
} else {
if (hw_intr_clear(udc, bit))
dev_err(dev, "invalid bit number\n");
}
spin_unlock_irqrestore(&udc->lock, flags);
done:
return count;
}
static DEVICE_ATTR(inters, S_IRUSR | S_IWUSR, show_inters, store_inters);
/**
* 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 *udc = container_of(dev, struct ci13xxx, gadget.dev);
unsigned long flags;
unsigned mode;
if (attr == NULL || buf == NULL) {
dev_err(udc->dev, "EINVAL\n");
return 0;
}
spin_lock_irqsave(&udc->lock, flags);
mode = hw_port_test_get(udc);
spin_unlock_irqrestore(&udc->lock, flags);
return scnprintf(buf, PAGE_SIZE, "mode = %u\n", mode);
}
/**
* 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)
{
struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
unsigned long flags;
unsigned mode;
if (attr == NULL || buf == NULL) {
dev_err(udc->dev, "[%s] EINVAL\n", __func__);
goto done;
}
if (sscanf(buf, "%u", &mode) != 1) {
dev_err(udc->dev, "<mode>: set port test mode");
goto done;
}
spin_lock_irqsave(&udc->lock, flags);
if (hw_port_test_set(udc, mode))
dev_err(udc->dev, "invalid mode\n");
spin_unlock_irqrestore(&udc->lock, flags);
done:
return count;
}
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)
{
struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
unsigned long flags;
unsigned i, j, n = 0;
if (attr == NULL || buf == NULL) {
dev_err(udc->dev, "[%s] EINVAL\n", __func__);
return 0;
}
spin_lock_irqsave(&udc->lock, flags);
for (i = 0; i < udc->hw_ep_max/2; i++) {
struct ci13xxx_ep *mEpRx = &udc->ci13xxx_ep[i];
struct ci13xxx_ep *mEpTx =
&udc->ci13xxx_ep[i + udc->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(&udc->lock, flags);
return n;
}
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)
{
struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
unsigned long flags;
u32 *dump;
unsigned i, k, n = 0;
if (attr == NULL || buf == NULL) {
dev_err(udc->dev, "[%s] EINVAL\n", __func__);
return 0;
}
dump = kmalloc(sizeof(u32) * DUMP_ENTRIES, GFP_KERNEL);
if (!dump) {
dev_err(udc->dev, "%s: out of memory\n", __func__);
return 0;
}
spin_lock_irqsave(&udc->lock, flags);
k = hw_register_read(udc, dump, DUMP_ENTRIES);
spin_unlock_irqrestore(&udc->lock, flags);
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);
return n;
}
/**
* 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 *udc = container_of(dev, struct ci13xxx, gadget.dev);
unsigned long addr, data, flags;
if (attr == NULL || buf == NULL) {
dev_err(udc->dev, "[%s] EINVAL\n", __func__);
goto done;
}
if (sscanf(buf, "%li %li", &addr, &data) != 2) {
dev_err(udc->dev,
"<addr> <data>: write data to register address\n");
goto done;
}
spin_lock_irqsave(&udc->lock, flags);
if (hw_register_write(udc, addr, data))
dev_err(udc->dev, "invalid address range\n");
spin_unlock_irqrestore(&udc->lock, flags);
done:
return count;
}
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)
{
struct ci13xxx *udc = 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(udc->dev, "[%s] EINVAL\n", __func__);
return 0;
}
spin_lock_irqsave(&udc->lock, flags);
for (i = 0; i < udc->hw_ep_max; i++)
list_for_each(ptr, &udc->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 % udc->hw_ep_max/2, (u32)req->dma,
((i < udc->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(&udc->lock, flags);
return n;
}
static DEVICE_ATTR(requests, S_IRUSR, show_requests, NULL);
/**
* dbg_create_files: initializes the attribute interface
* @dev: device
*
* This function returns an error code
*/
int dbg_create_files(struct device *dev)
{
int retval = 0;
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;
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;
}
/**
* dbg_remove_files: destroys the attribute interface
* @dev: device
*
* This function returns an error code
*/
int dbg_remove_files(struct device *dev)
{
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;
}
/*
* debug.h - ChipIdea USB driver debug interfaces
*
* Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved.
*
* Author: David Lopo
*
* 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_DEBUG_H
#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);
#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)
{
return 0;
}
static inline int dbg_remove_files(struct device *dev)
{
return 0;
}
#endif
#endif /* __DRIVERS_USB_CHIPIDEA_DEBUG_H */
/*
* ci13xxx_udc.c - MIPS USB IP core family device controller
* udc.h - ChipIdea UDC driver
*
* Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved.
*
......@@ -10,46 +10,6 @@
* published by the Free Software Foundation.
*/
/*
* Description: MIPS USB IP core family device controller
* Currently it only supports IP part number CI13412
*
* This driver is composed of several blocks:
* - HW: hardware interface
* - DBG: debug facilities (optional)
* - UTIL: utilities
* - ISR: interrupts handling
* - ENDPT: endpoint operations (Gadget API)
* - GADGET: gadget operations (Gadget API)
* - BUS: bus glue code, bus abstraction layer
*
* Compile Options
* - CONFIG_USB_GADGET_DEBUG_FILES: 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
* => case 5: Hi > Di
* => case 8: Hi <> Do
* if undefined usbtest 13 fails
* - TRACE: enable function tracing (depends on DEBUG)
*
* Main Features
* - Chapter 9 & Mass Storage Compliance with Gadget File Storage
* - Chapter 9 Compliance with Gadget Zero (STALL_IN undefined)
* - Normal & LPM support
*
* USBTEST Report
* - OK: 0-12, 13 (STALL_IN defined) & 14
* - 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>
......@@ -66,14 +26,12 @@
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/otg.h>
#include <linux/usb/chipidea.h>
#include "ci13xxx_udc.h"
/******************************************************************************
* DEFINE
*****************************************************************************/
#define DMA_ADDR_INVALID (~(dma_addr_t)0)
#include "ci.h"
#include "udc.h"
#include "bits.h"
#include "debug.h"
/* control endpoint description */
static const struct usb_endpoint_descriptor
......@@ -96,112 +54,6 @@ ctrl_endpt_in_desc = {
.wMaxPacketSize = cpu_to_le16(CTRL_PAYLOAD_MAX),
};
/* Interrupt statistics */
#define ISR_MASK 0x1F
static struct {
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;
/**
* ffs_nr: find first (least significant) bit set
* @x: the word to search
*
* This function returns bit number (instead of position)
*/
static int ffs_nr(u32 x)
{
int n = ffs(x);
return n ? n-1 : 32;
}
/******************************************************************************
* HW block
*****************************************************************************/
/* MSM specific */
#define ABS_AHBBURST (0x0090UL)
#define ABS_AHBMODE (0x0098UL)
/* UDC 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_USBMODE] = 0x068UL,
[OP_ENDPTSETUPSTAT] = 0x06CUL,
[OP_ENDPTPRIME] = 0x070UL,
[OP_ENDPTFLUSH] = 0x074UL,
[OP_ENDPTSTAT] = 0x078UL,
[OP_ENDPTCOMPLETE] = 0x07CUL,
[OP_ENDPTCTRL] = 0x080UL,
};
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_USBMODE] = 0x0C8UL,
[OP_ENDPTSETUPSTAT] = 0x0D8UL,
[OP_ENDPTPRIME] = 0x0DCUL,
[OP_ENDPTFLUSH] = 0x0E0UL,
[OP_ENDPTSTAT] = 0x0E4UL,
[OP_ENDPTCOMPLETE] = 0x0E8UL,
[OP_ENDPTCTRL] = 0x0ECUL,
};
static int hw_alloc_regmap(struct ci13xxx *udc, bool is_lpm)
{
int i;
kfree(udc->hw_bank.regmap);
udc->hw_bank.regmap = kzalloc((OP_LAST + 1) * sizeof(void *),
GFP_KERNEL);
if (!udc->hw_bank.regmap)
return -ENOMEM;
for (i = 0; i < OP_ENDPTCTRL; i++)
udc->hw_bank.regmap[i] =
(i <= CAP_LAST ? udc->hw_bank.cap : udc->hw_bank.op) +
(is_lpm ? ci_regs_lpm[i] : ci_regs_nolpm[i]);
for (; i <= OP_LAST; i++)
udc->hw_bank.regmap[i] = udc->hw_bank.op +
4 * (i - OP_ENDPTCTRL) +
(is_lpm
? ci_regs_lpm[OP_ENDPTCTRL]
: ci_regs_nolpm[OP_ENDPTCTRL]);
return 0;
}
/**
* hw_ep_bit: calculates the bit number
* @num: endpoint number
......@@ -214,7 +66,7 @@ static inline int hw_ep_bit(int num, int dir)
return num + (dir ? 16 : 0);
}
static int ep_to_bit(struct ci13xxx *udc, int n)
static inline int ep_to_bit(struct ci13xxx *udc, int n)
{
int fill = 16 - udc->hw_ep_max / 2;
......@@ -224,145 +76,6 @@ static int ep_to_bit(struct ci13xxx *udc, int n)
return n;
}
/**
* hw_read: reads from a hw register
* @reg: register index
* @mask: bitfield mask
*
* This function returns register contents
*/
static u32 hw_read(struct ci13xxx *udc, enum ci13xxx_regs reg, u32 mask)
{
return ioread32(udc->hw_bank.regmap[reg]) & mask;
}
/**
* hw_write: writes to a hw register
* @reg: register index
* @mask: bitfield mask
* @data: new value
*/
static void hw_write(struct ci13xxx *udc, enum ci13xxx_regs reg, u32 mask,
u32 data)
{
if (~mask)
data = (ioread32(udc->hw_bank.regmap[reg]) & ~mask)
| (data & mask);
iowrite32(data, udc->hw_bank.regmap[reg]);
}
/**
* hw_test_and_clear: tests & clears a hw register
* @reg: register index
* @mask: bitfield mask
*
* This function returns register contents
*/
static u32 hw_test_and_clear(struct ci13xxx *udc, enum ci13xxx_regs reg,
u32 mask)
{
u32 val = ioread32(udc->hw_bank.regmap[reg]) & mask;
iowrite32(val, udc->hw_bank.regmap[reg]);
return val;
}
/**
* hw_test_and_write: tests & writes a hw register
* @reg: register index
* @mask: bitfield mask
* @data: new value
*
* This function returns register contents
*/
static u32 hw_test_and_write(struct ci13xxx *udc, enum ci13xxx_regs reg,
u32 mask, u32 data)
{
u32 val = hw_read(udc, reg, ~0);
hw_write(udc, reg, mask, data);
return (val & mask) >> ffs_nr(mask);
}
static int hw_device_init(struct ci13xxx *udc, void __iomem *base,
uintptr_t cap_offset)
{
u32 reg;
/* bank is a module variable */
udc->hw_bank.abs = base;
udc->hw_bank.cap = udc->hw_bank.abs;
udc->hw_bank.cap += cap_offset;
udc->hw_bank.op = udc->hw_bank.cap + ioread8(udc->hw_bank.cap);
hw_alloc_regmap(udc, false);
reg = hw_read(udc, CAP_HCCPARAMS, HCCPARAMS_LEN) >>
ffs_nr(HCCPARAMS_LEN);
udc->hw_bank.lpm = reg;
hw_alloc_regmap(udc, !!reg);
udc->hw_bank.size = udc->hw_bank.op - udc->hw_bank.abs;
udc->hw_bank.size += OP_LAST;
udc->hw_bank.size /= sizeof(u32);
reg = hw_read(udc, CAP_DCCPARAMS, DCCPARAMS_DEN) >>
ffs_nr(DCCPARAMS_DEN);
udc->hw_ep_max = reg * 2; /* cache hw ENDPT_MAX */
if (udc->hw_ep_max == 0 || udc->hw_ep_max > ENDPT_MAX)
return -ENODEV;
dev_dbg(udc->dev, "ChipIdea UDC found, lpm: %d; cap: %p op: %p\n",
udc->hw_bank.lpm, udc->hw_bank.cap, udc->hw_bank.op);
/* setup lock mode ? */
/* ENDPTSETUPSTAT is '0' by default */
/* HCSPARAMS.bf.ppc SHOULD BE zero for device */
return 0;
}
/**
* hw_device_reset: resets chip (execute without interruption)
* @base: register base address
*
* This function returns an error code
*/
static int hw_device_reset(struct ci13xxx *udc)
{
/* should flush & stop before reset */
hw_write(udc, OP_ENDPTFLUSH, ~0, ~0);
hw_write(udc, OP_USBCMD, USBCMD_RS, 0);
hw_write(udc, OP_USBCMD, USBCMD_RST, USBCMD_RST);
while (hw_read(udc, OP_USBCMD, USBCMD_RST))
udelay(10); /* not RTOS friendly */
if (udc->udc_driver->notify_event)
udc->udc_driver->notify_event(udc,
CI13XXX_CONTROLLER_RESET_EVENT);
if (udc->udc_driver->flags & CI13XXX_DISABLE_STREAMING)
hw_write(udc, OP_USBMODE, USBMODE_SDIS, USBMODE_SDIS);
/* USBMODE should be configured step by step */
hw_write(udc, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE);
hw_write(udc, OP_USBMODE, USBMODE_CM, USBMODE_CM_DEVICE);
/* HW >= 2.3 */
hw_write(udc, OP_USBMODE, USBMODE_SLOM, USBMODE_SLOM);
if (hw_read(udc, OP_USBMODE, USBMODE_CM) != USBMODE_CM_DEVICE) {
pr_err("cannot enter in device mode");
pr_err("lpm = %i", udc->hw_bank.lpm);
return -ENODEV;
}
return 0;
}
/**
* hw_device_state: enables/disables interrupts & starts/stops device (execute
* without interruption)
......@@ -536,42 +249,6 @@ static int hw_ep_set_halt(struct ci13xxx *udc, int num, int dir, int value)
return 0;
}
/**
* hw_intr_clear: disables interrupt & clears interrupt status (execute without
* interruption)
* @n: interrupt bit
*
* This function returns an error code
*/
static int hw_intr_clear(struct ci13xxx *udc, int n)
{
if (n >= REG_BITS)
return -EINVAL;
hw_write(udc, OP_USBINTR, BIT(n), 0);
hw_write(udc, OP_USBSTS, BIT(n), BIT(n));
return 0;
}
/**
* 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 *udc, int n)
{
if (n >= REG_BITS)
return -EINVAL;
hw_write(udc, CAP_TESTMODE, TESTMODE_FORCE, TESTMODE_FORCE);
hw_write(udc, OP_USBINTR, BIT(n), BIT(n));
hw_write(udc, OP_USBSTS, BIT(n), BIT(n));
hw_write(udc, CAP_TESTMODE, TESTMODE_FORCE, 0);
return 0;
}
/**
* hw_is_port_high_speed: test if port is high speed
*
......@@ -583,33 +260,6 @@ static int hw_port_is_high_speed(struct ci13xxx *udc)
hw_read(udc, OP_PORTSC, PORTSC_HSP);
}
/**
* hw_port_test_get: reads port test mode value
*
* This function returns port test mode value
*/
static u8 hw_port_test_get(struct ci13xxx *udc)
{
return hw_read(udc, OP_PORTSC, PORTSC_PTC) >> ffs_nr(PORTSC_PTC);
}
/**
* hw_port_test_set: writes port test mode (execute without interruption)
* @mode: new value
*
* This function returns an error code
*/
static int hw_port_test_set(struct ci13xxx *udc, u8 mode)
{
const u8 TEST_MODE_MAX = 7;
if (mode > TEST_MODE_MAX)
return -EINVAL;
hw_write(udc, OP_PORTSC, PORTSC_PTC, mode << ffs_nr(PORTSC_PTC));
return 0;
}
/**
* hw_read_intr_enable: returns interrupt enable register
*
......@@ -630,48 +280,6 @@ static u32 hw_read_intr_status(struct ci13xxx *udc)
return hw_read(udc, OP_USBSTS, ~0);
}
/**
* hw_register_read: reads all device registers (execute without interruption)
* @buf: destination buffer
* @size: buffer size
*
* This function returns number of registers read
*/
static size_t hw_register_read(struct ci13xxx *udc, u32 *buf, size_t size)
{
unsigned i;
if (size > udc->hw_bank.size)
size = udc->hw_bank.size;
for (i = 0; i < size; i++)
buf[i] = hw_read(udc, i * sizeof(u32), ~0);
return size;
}
/**
* hw_register_write: writes to register
* @addr: register address
* @data: register value
*
* This function returns an error code
*/
static int hw_register_write(struct ci13xxx *udc, u16 addr, u32 data)
{
/* align */
addr /= sizeof(u32);
if (addr >= udc->hw_bank.size)
return -EINVAL;
/* align */
addr *= sizeof(u32);
hw_write(udc, addr, ~0, data);
return 0;
}
/**
* hw_test_and_clear_complete: test & clear complete status (execute without
* interruption)
......@@ -765,673 +373,6 @@ static int hw_usb_reset(struct ci13xxx *udc)
return 0;
}
/******************************************************************************
* DBG block
*****************************************************************************/
/**
* 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)
{
struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
struct usb_gadget *gadget = &udc->gadget;
int n = 0;
if (attr == NULL || buf == NULL) {
dev_err(udc->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);
/* TODO: Scheduled for removal in 3.8. */
n += scnprintf(buf + n, PAGE_SIZE - n, "is_dualspeed = %d\n",
gadget_is_dualspeed(gadget));
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;
}
static DEVICE_ATTR(device, S_IRUSR, show_device, NULL);
/**
* show_driver: prints information about attached gadget (if any)
*
* Check "device.h" for details
*/
static ssize_t show_driver(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
struct usb_gadget_driver *driver = udc->driver;
int n = 0;
if (attr == NULL || buf == NULL) {
dev_err(dev, "[%s] EINVAL\n", __func__);
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);
return n;
}
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)
{
*idx = (*idx - 1) & (DBG_DATA_MAX-1);
}
/**
* dbg_inc: increments debug event index
* @idx: buffer index
*/
static void dbg_inc(unsigned *idx)
{
*idx = (*idx + 1) & (DBG_DATA_MAX-1);
}
/**
* dbg_print: prints the common part of the event
* @addr: endpoint address
* @name: event name
* @status: status
* @extra: extra information
*/
static void dbg_print(u8 addr, const char *name, int status, const char *extra)
{
struct timeval tval;
unsigned int stamp;
unsigned long flags;
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
*/
static void dbg_done(u8 addr, const u32 token, int status)
{
char msg[DBG_DATA_MSG];
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);
}
/**
* dbg_event: prints a generic event
* @addr: endpoint address
* @name: event name
* @status: status
*/
static void dbg_event(u8 addr, const char *name, int status)
{
if (name != NULL)
dbg_print(addr, name, status, "");
}
/*
* dbg_queue: prints a QUEUE event
* @addr: endpoint address
* @req: USB request
* @status: status
*/
static void dbg_queue(u8 addr, const struct usb_request *req, int status)
{
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);
}
}
/**
* dbg_setup: prints a SETUP event
* @addr: endpoint address
* @req: setup request
*/
static 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);
}
}
/**
* 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)
{
unsigned long flags;
unsigned i, j, n = 0;
if (attr == NULL || buf == NULL) {
dev_err(dev->parent, "[%s] EINVAL\n", __func__);
return 0;
}
read_lock_irqsave(&dbg_data.lck, flags);
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]);
read_unlock_irqrestore(&dbg_data.lck, flags);
return n;
}
/**
* 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;
if (attr == NULL || buf == NULL) {
dev_err(dev, "[%s] EINVAL\n", __func__);
goto done;
}
if (sscanf(buf, "%u", &tty) != 1 || tty > 1) {
dev_err(dev, "<1|0>: enable|disable console log\n");
goto done;
}
dbg_data.tty = tty;
dev_info(dev, "tty = %u", dbg_data.tty);
done:
return count;
}
static DEVICE_ATTR(events, S_IRUSR | S_IWUSR, show_events, store_events);
/**
* 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 *udc = container_of(dev, struct ci13xxx, gadget.dev);
unsigned long flags;
u32 intr;
unsigned i, j, n = 0;
if (attr == NULL || buf == NULL) {
dev_err(udc->dev, "[%s] EINVAL\n", __func__);
return 0;
}
spin_lock_irqsave(&udc->lock, flags);
n += scnprintf(buf + n, PAGE_SIZE - n,
"status = %08x\n", hw_read_intr_status(udc));
n += scnprintf(buf + n, PAGE_SIZE - n,
"enable = %08x\n", hw_read_intr_enable(udc));
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");
}
spin_unlock_irqrestore(&udc->lock, flags);
return n;
}
/**
* 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 *udc = container_of(dev, struct ci13xxx, gadget.dev);
unsigned long flags;
unsigned en, bit;
if (attr == NULL || buf == NULL) {
dev_err(udc->dev, "EINVAL\n");
goto done;
}
if (sscanf(buf, "%u %u", &en, &bit) != 2 || en > 1) {
dev_err(udc->dev, "<1|0> <bit>: enable|disable interrupt\n");
goto done;
}
spin_lock_irqsave(&udc->lock, flags);
if (en) {
if (hw_intr_force(udc, bit))
dev_err(dev, "invalid bit number\n");
else
isr_statistics.test++;
} else {
if (hw_intr_clear(udc, bit))
dev_err(dev, "invalid bit number\n");
}
spin_unlock_irqrestore(&udc->lock, flags);
done:
return count;
}
static DEVICE_ATTR(inters, S_IRUSR | S_IWUSR, show_inters, store_inters);
/**
* 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 *udc = container_of(dev, struct ci13xxx, gadget.dev);
unsigned long flags;
unsigned mode;
if (attr == NULL || buf == NULL) {
dev_err(udc->dev, "EINVAL\n");
return 0;
}
spin_lock_irqsave(&udc->lock, flags);
mode = hw_port_test_get(udc);
spin_unlock_irqrestore(&udc->lock, flags);
return scnprintf(buf, PAGE_SIZE, "mode = %u\n", mode);
}
/**
* 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)
{
struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
unsigned long flags;
unsigned mode;
if (attr == NULL || buf == NULL) {
dev_err(udc->dev, "[%s] EINVAL\n", __func__);
goto done;
}
if (sscanf(buf, "%u", &mode) != 1) {
dev_err(udc->dev, "<mode>: set port test mode");
goto done;
}
spin_lock_irqsave(&udc->lock, flags);
if (hw_port_test_set(udc, mode))
dev_err(udc->dev, "invalid mode\n");
spin_unlock_irqrestore(&udc->lock, flags);
done:
return count;
}
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)
{
struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
unsigned long flags;
unsigned i, j, n = 0;
if (attr == NULL || buf == NULL) {
dev_err(udc->dev, "[%s] EINVAL\n", __func__);
return 0;
}
spin_lock_irqsave(&udc->lock, flags);
for (i = 0; i < udc->hw_ep_max/2; i++) {
struct ci13xxx_ep *mEpRx = &udc->ci13xxx_ep[i];
struct ci13xxx_ep *mEpTx =
&udc->ci13xxx_ep[i + udc->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(&udc->lock, flags);
return n;
}
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)
{
struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
unsigned long flags;
u32 *dump;
unsigned i, k, n = 0;
if (attr == NULL || buf == NULL) {
dev_err(udc->dev, "[%s] EINVAL\n", __func__);
return 0;
}
dump = kmalloc(sizeof(u32) * DUMP_ENTRIES, GFP_KERNEL);
if (!dump) {
dev_err(udc->dev, "%s: out of memory\n", __func__);
return 0;
}
spin_lock_irqsave(&udc->lock, flags);
k = hw_register_read(udc, dump, DUMP_ENTRIES);
spin_unlock_irqrestore(&udc->lock, flags);
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);
return n;
}
/**
* 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 *udc = container_of(dev, struct ci13xxx, gadget.dev);
unsigned long addr, data, flags;
if (attr == NULL || buf == NULL) {
dev_err(udc->dev, "[%s] EINVAL\n", __func__);
goto done;
}
if (sscanf(buf, "%li %li", &addr, &data) != 2) {
dev_err(udc->dev,
"<addr> <data>: write data to register address\n");
goto done;
}
spin_lock_irqsave(&udc->lock, flags);
if (hw_register_write(udc, addr, data))
dev_err(udc->dev, "invalid address range\n");
spin_unlock_irqrestore(&udc->lock, flags);
done:
return count;
}
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)
{
struct ci13xxx *udc = 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(udc->dev, "[%s] EINVAL\n", __func__);
return 0;
}
spin_lock_irqsave(&udc->lock, flags);
for (i = 0; i < udc->hw_ep_max; i++)
list_for_each(ptr, &udc->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 % udc->hw_ep_max/2, (u32)req->dma,
((i < udc->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(&udc->lock, flags);
return n;
}
static DEVICE_ATTR(requests, S_IRUSR, show_requests, NULL);
/**
* dbg_create_files: initializes the attribute interface
* @dev: device
*
* This function returns an error code
*/
__maybe_unused static int dbg_create_files(struct device *dev)
{
int retval = 0;
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;
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;
}
/**
* dbg_remove_files: destroys the attribute interface
* @dev: device
*
* This function returns an error code
*/
__maybe_unused static int dbg_remove_files(struct device *dev)
{
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;
}
/******************************************************************************
* UTIL block
*****************************************************************************/
......@@ -2656,7 +1597,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(int irq, void *data)
irqreturn_t udc_irq(int irq, void *data)
{
struct ci13xxx *udc = data;
irqreturn_t retval;
......@@ -2675,18 +1616,14 @@ static irqreturn_t udc_irq(int irq, void *data)
}
}
intr = hw_test_and_clear_intr_active(udc);
if (intr) {
isr_statistics.hndl.buf[isr_statistics.hndl.idx++] = intr;
isr_statistics.hndl.idx &= ISR_MASK;
isr_statistics.hndl.cnt++;
dbg_interrupt(intr);
if (intr) {
/* order defines priority - do NOT change it */
if (USBi_URI & intr) {
isr_statistics.uri++;
if (USBi_URI & intr)
isr_reset_handler(udc);
}
if (USBi_PCI & intr) {
isr_statistics.pci++;
udc->gadget.speed = hw_port_is_high_speed(udc) ?
USB_SPEED_HIGH : USB_SPEED_FULL;
if (udc->suspended && udc->driver->resume) {
......@@ -2696,12 +1633,10 @@ static irqreturn_t udc_irq(int irq, void *data)
udc->suspended = 0;
}
}
if (USBi_UEI & intr)
isr_statistics.uei++;
if (USBi_UI & intr) {
isr_statistics.ui++;
if (USBi_UI & intr)
isr_tr_complete_handler(udc);
}
if (USBi_SLI & intr) {
if (udc->gadget.speed != USB_SPEED_UNKNOWN &&
udc->driver->suspend) {
......@@ -2710,11 +1645,9 @@ static irqreturn_t udc_irq(int irq, void *data)
udc->driver->suspend(&udc->gadget);
spin_lock(&udc->lock);
}
isr_statistics.sli++;
}
retval = IRQ_HANDLED;
} else {
isr_statistics.none++;
retval = IRQ_NONE;
}
spin_unlock(&udc->lock);
......@@ -2742,8 +1675,8 @@ static void udc_release(struct device *dev)
* No interrupts active, the IRQ has not been requested yet
* Kernel assumes 32-bit DMA operations by default, no need to dma_set_mask
*/
static int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev,
void __iomem *regs, struct ci13xxx **_udc)
int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev,
void __iomem *regs, struct ci13xxx **_udc)
{
struct ci13xxx *udc;
int retval = 0;
......@@ -2824,9 +1757,7 @@ static int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev,
goto put_transceiver;
}
#ifdef CONFIG_USB_GADGET_DEBUG_FILES
retval = dbg_create_files(&udc->gadget.dev);
#endif
if (retval)
goto unreg_device;
......@@ -2855,9 +1786,7 @@ static int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev,
dev_err(dev, "error = %i\n", retval);
remove_dbg:
#ifdef CONFIG_USB_GADGET_DEBUG_FILES
dbg_remove_files(&udc->gadget.dev);
#endif
unreg_device:
device_unregister(&udc->gadget.dev);
put_transceiver:
......@@ -2878,7 +1807,7 @@ static int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev,
*
* No interrupts active, the IRQ has been released
*/
static void udc_remove(struct ci13xxx *udc)
void udc_remove(struct ci13xxx *udc)
{
int i;
......@@ -2900,84 +1829,9 @@ static void udc_remove(struct ci13xxx *udc)
otg_set_peripheral(udc->transceiver->otg, &udc->gadget);
usb_put_transceiver(udc->transceiver);
}
#ifdef CONFIG_USB_GADGET_DEBUG_FILES
dbg_remove_files(&udc->gadget.dev);
#endif
device_unregister(&udc->gadget.dev);
kfree(udc->hw_bank.regmap);
kfree(udc);
}
static int __devinit ci_udc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ci13xxx_udc_driver *driver = dev->platform_data;
struct ci13xxx *udc;
struct resource *res;
void __iomem *base;
int ret;
if (!driver) {
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;
}
ret = udc_probe(driver, dev, base, &udc);
if (ret)
return ret;
udc->irq = platform_get_irq(pdev, 0);
if (udc->irq < 0) {
dev_err(dev, "missing IRQ\n");
ret = -ENODEV;
goto out;
}
platform_set_drvdata(pdev, udc);
ret = request_irq(udc->irq, udc_irq, IRQF_SHARED, driver->name, udc);
out:
if (ret)
udc_remove(udc);
return ret;
}
static int __devexit ci_udc_remove(struct platform_device *pdev)
{
struct ci13xxx *udc = platform_get_drvdata(pdev);
free_irq(udc->irq, udc);
udc_remove(udc);
return 0;
}
static struct platform_driver ci_udc_driver = {
.probe = ci_udc_probe,
.remove = __devexit_p(ci_udc_remove),
.driver = {
.name = "ci_udc",
},
};
module_platform_driver(ci_udc_driver);
MODULE_ALIAS("platform:ci_udc");
MODULE_ALIAS("platform:ci13xxx");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("David Lopo <dlopo@chipidea.mips.com>");
MODULE_DESCRIPTION("ChipIdea UDC Driver");
/*
* udc.h - ChipIdea UDC structures
*
* Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved.
*
* Author: David Lopo
*
* 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_UDC_H
#define __DRIVERS_USB_CHIPIDEA_UDC_H
#include <linux/list.h>
#define CTRL_PAYLOAD_MAX 64
#define RX 0 /* similar to USB_DIR_OUT but can be used as an index */
#define TX 1 /* similar to USB_DIR_IN but can be used as an index */
/* DMA layout of transfer descriptors */
struct ci13xxx_td {
/* 0 */
u32 next;
#define TD_TERMINATE BIT(0)
#define TD_ADDR_MASK (0xFFFFFFEUL << 5)
/* 1 */
u32 token;
#define TD_STATUS (0x00FFUL << 0)
#define TD_STATUS_TR_ERR BIT(3)
#define TD_STATUS_DT_ERR BIT(5)
#define TD_STATUS_HALTED BIT(6)
#define TD_STATUS_ACTIVE BIT(7)
#define TD_MULTO (0x0003UL << 10)
#define TD_IOC BIT(15)
#define TD_TOTAL_BYTES (0x7FFFUL << 16)
/* 2 */
u32 page[5];
#define TD_CURR_OFFSET (0x0FFFUL << 0)
#define TD_FRAME_NUM (0x07FFUL << 0)
#define TD_RESERVED_MASK (0x0FFFUL << 0)
} __attribute__ ((packed));
/* DMA layout of queue heads */
struct ci13xxx_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)
/* 1 */
u32 curr;
/* 2 - 8 */
struct ci13xxx_td td;
/* 9 */
u32 RESERVED;
struct usb_ctrlrequest setup;
} __attribute__ ((packed));
/* Extension of usb_request */
struct ci13xxx_req {
struct usb_request req;
unsigned map;
struct list_head queue;
struct ci13xxx_td *ptr;
dma_addr_t dma;
struct ci13xxx_td *zptr;
dma_addr_t zdma;
};
#ifdef CONFIG_USB_CHIPIDEA_UDC
irqreturn_t udc_irq(int irq, void *data);
int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev,
void __iomem *regs, struct ci13xxx **_udc);
void udc_remove(struct ci13xxx *udc);
#else
static inline irqreturn_t udc_irq(int irq, void *data)
{
return IRQ_NONE;
}
static inline
int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev,
void __iomem *regs, struct ci13xxx **_udc)
{
return -ENODEV;
}
static inline void udc_remove(struct ci13xxx *udc)
{
}
#endif
#endif /* __DRIVERS_USB_CHIPIDEA_UDC_H */
/*
* Platform data for the chipidea USB dual role controller
*/
#ifndef __LINUX_USB_CHIPIDEA_H
#define __LINUX_USB_CHIPIDEA_H
struct ci13xxx;
struct ci13xxx_udc_driver {
const char *name;
/* offset of the capability registers */
uintptr_t capoffset;
unsigned long flags;
#define CI13XXX_REGS_SHARED BIT(0)
#define CI13XXX_REQUIRE_TRANSCEIVER BIT(1)
#define CI13XXX_PULLUP_ON_VBUS BIT(2)
#define CI13XXX_DISABLE_STREAMING BIT(3)
#define CI13XXX_CONTROLLER_RESET_EVENT 0
#define CI13XXX_CONTROLLER_STOPPED_EVENT 1
void (*notify_event) (struct ci13xxx *udc, unsigned event);
};
/* Default offset of capability registers */
#define DEF_CAPOFFSET 0x100
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment