Commit f1407d5c authored by Kuninori Morimoto's avatar Kuninori Morimoto Committed by Greg Kroah-Hartman

usb: renesas_usbhs: Add Renesas USBHS common code

Renesas SuperH has USBHS IP which can switch Host / Function.
This driver is designed so that Host / Function may dynamically change.
This patch add usb/renesas_usbhs and common code for SuperH USBHS.
Signed-off-by: default avatarKuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent a6360dd3
......@@ -67,6 +67,7 @@ obj-$(CONFIG_UWB) += uwb/
obj-$(CONFIG_USB_OTG_UTILS) += usb/otg/
obj-$(CONFIG_USB) += usb/
obj-$(CONFIG_USB_MUSB_HDRC) += usb/musb/
obj-$(CONFIG_USB_RENESAS_USBHS) += usb/renesas_usbhs/
obj-$(CONFIG_PCI) += usb/
obj-$(CONFIG_USB_GADGET) += usb/gadget/
obj-$(CONFIG_SERIO) += input/serio/
......
......@@ -115,6 +115,8 @@ source "drivers/usb/host/Kconfig"
source "drivers/usb/musb/Kconfig"
source "drivers/usb/renesas_usbhs/Kconfig"
source "drivers/usb/class/Kconfig"
source "drivers/usb/storage/Kconfig"
......
#
# Renesas USB Controller Drivers
#
config USB_RENESAS_USBHS
tristate 'Renesas USBHS controller'
default n
help
Renesas USBHS is a discrete USB host and peripheral controller chip
that supports both full and high speed USB 2.0 data transfers.
It has nine or more configurable endpoints, and endpoint zero.
Say "y" to link the driver statically, or "m" to build a
dynamically linked module called "renesas_usbhs" and force all
gadget drivers to also be dynamically linked.
#
# for Renesas USB
#
obj-$(CONFIG_USB_RENESAS_USBHS) += renesas_usbhs.o
renesas_usbhs-y := common.o mod.o pipe.o
/*
* Renesas USB driver
*
* Copyright (C) 2011 Renesas Solutions Corp.
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include "./common.h"
/*
* platform call back
*
* renesas usb support platform callback function.
* Below macro call it.
* if platform doesn't have callback, it return 0 (no error)
*/
#define usbhs_platform_call(priv, func, args...)\
(!(priv) ? -ENODEV : \
!((priv)->pfunc->func) ? 0 : \
(priv)->pfunc->func(args))
/*
* common functions
*/
u16 usbhs_read(struct usbhs_priv *priv, u32 reg)
{
return ioread16(priv->base + reg);
}
void usbhs_write(struct usbhs_priv *priv, u32 reg, u16 data)
{
iowrite16(data, priv->base + reg);
}
void usbhs_bset(struct usbhs_priv *priv, u32 reg, u16 mask, u16 data)
{
u16 val = usbhs_read(priv, reg);
val &= ~mask;
val |= data & mask;
usbhs_write(priv, reg, val);
}
/*
* syscfg functions
*/
void usbhs_sys_clock_ctrl(struct usbhs_priv *priv, int enable)
{
usbhs_bset(priv, SYSCFG, SCKE, enable ? SCKE : 0);
}
void usbhs_sys_hispeed_ctrl(struct usbhs_priv *priv, int enable)
{
usbhs_bset(priv, SYSCFG, HSE, enable ? HSE : 0);
}
void usbhs_sys_usb_ctrl(struct usbhs_priv *priv, int enable)
{
usbhs_bset(priv, SYSCFG, USBE, enable ? USBE : 0);
}
void usbhs_sys_host_ctrl(struct usbhs_priv *priv, int enable)
{
u16 mask = DCFM | DRPD | DPRPU;
u16 val = DCFM | DRPD;
/*
* if enable
*
* - select Host mode
* - D+ Line/D- Line Pull-down
*/
usbhs_bset(priv, SYSCFG, mask, enable ? val : 0);
}
void usbhs_sys_function_ctrl(struct usbhs_priv *priv, int enable)
{
u16 mask = DCFM | DRPD | DPRPU;
u16 val = DPRPU;
/*
* if enable
*
* - select Function mode
* - D+ Line Pull-up
*/
usbhs_bset(priv, SYSCFG, mask, enable ? val : 0);
}
/*
* frame functions
*/
int usbhs_frame_get_num(struct usbhs_priv *priv)
{
return usbhs_read(priv, FRMNUM) & FRNM_MASK;
}
/*
* local functions
*/
static struct usbhs_priv *usbhsc_pdev_to_priv(struct platform_device *pdev)
{
return dev_get_drvdata(&pdev->dev);
}
static void usbhsc_bus_ctrl(struct usbhs_priv *priv, int enable)
{
int wait = usbhs_get_dparam(priv, buswait_bwait);
u16 data = 0;
if (enable) {
/* set bus wait if platform have */
if (wait)
usbhs_bset(priv, BUSWAIT, 0x000F, wait);
}
usbhs_write(priv, DVSTCTR, data);
}
/*
* platform default param
*/
static u32 usbhsc_default_pipe_type[] = {
USB_ENDPOINT_XFER_CONTROL,
USB_ENDPOINT_XFER_ISOC,
USB_ENDPOINT_XFER_ISOC,
USB_ENDPOINT_XFER_BULK,
USB_ENDPOINT_XFER_BULK,
USB_ENDPOINT_XFER_BULK,
USB_ENDPOINT_XFER_INT,
USB_ENDPOINT_XFER_INT,
USB_ENDPOINT_XFER_INT,
USB_ENDPOINT_XFER_INT,
};
/*
* driver callback functions
*/
static void usbhsc_notify_hotplug(struct work_struct *work)
{
struct usbhs_priv *priv = container_of(work,
struct usbhs_priv,
notify_hotplug_work);
struct platform_device *pdev = usbhs_priv_to_pdev(priv);
struct usbhs_mod *mod = usbhs_mod_get_current(priv);
int id;
int enable;
int ret;
/*
* get vbus status from platform
*/
enable = usbhs_platform_call(priv, get_vbus, pdev);
/*
* get id from platform
*/
id = usbhs_platform_call(priv, get_id, pdev);
if (enable && !mod) {
ret = usbhs_mod_change(priv, id);
if (ret < 0)
return;
dev_dbg(&pdev->dev, "%s enable\n", __func__);
/* enable PM */
pm_runtime_get_sync(&pdev->dev);
/* USB on */
usbhs_sys_clock_ctrl(priv, enable);
usbhsc_bus_ctrl(priv, enable);
/* module start */
usbhs_mod_call(priv, start, priv);
} else if (!enable && mod) {
dev_dbg(&pdev->dev, "%s disable\n", __func__);
/* module stop */
usbhs_mod_call(priv, stop, priv);
/* USB off */
usbhsc_bus_ctrl(priv, enable);
usbhs_sys_clock_ctrl(priv, enable);
/* disable PM */
pm_runtime_put_sync(&pdev->dev);
usbhs_mod_change(priv, -1);
/* reset phy for next connection */
usbhs_platform_call(priv, phy_reset, pdev);
}
}
static int usbhsc_drvcllbck_notify_hotplug(struct platform_device *pdev)
{
struct usbhs_priv *priv = usbhsc_pdev_to_priv(pdev);
/*
* This functions will be called in interrupt.
* To make sure safety context,
* use workqueue for usbhs_notify_hotplug
*/
schedule_work(&priv->notify_hotplug_work);
return 0;
}
/*
* platform functions
*/
static int __devinit usbhs_probe(struct platform_device *pdev)
{
struct renesas_usbhs_platform_info *info = pdev->dev.platform_data;
struct renesas_usbhs_driver_callback *dfunc;
struct usbhs_priv *priv;
struct resource *res;
unsigned int irq;
int ret;
/* check platform information */
if (!info ||
!info->platform_callback.get_id ||
!info->platform_callback.get_vbus) {
dev_err(&pdev->dev, "no platform information\n");
return -EINVAL;
}
/* platform data */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq = platform_get_irq(pdev, 0);
if (!res || (int)irq <= 0) {
dev_err(&pdev->dev, "Not enough Renesas USB platform resources.\n");
return -ENODEV;
}
/* usb private data */
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(&pdev->dev, "Could not allocate priv\n");
return -ENOMEM;
}
priv->base = ioremap_nocache(res->start, resource_size(res));
if (!priv->base) {
dev_err(&pdev->dev, "ioremap error.\n");
ret = -ENOMEM;
goto probe_end_kfree;
}
/*
* care platform info
*/
priv->pfunc = &info->platform_callback;
priv->dparam = &info->driver_param;
/* set driver callback functions for platform */
dfunc = &info->driver_callback;
dfunc->notify_hotplug = usbhsc_drvcllbck_notify_hotplug;
/* set default param if platform doesn't have */
if (!priv->dparam->pipe_type) {
priv->dparam->pipe_type = usbhsc_default_pipe_type;
priv->dparam->pipe_size = ARRAY_SIZE(usbhsc_default_pipe_type);
}
/*
* priv settings
*/
priv->irq = irq;
priv->pdev = pdev;
INIT_WORK(&priv->notify_hotplug_work, usbhsc_notify_hotplug);
spin_lock_init(usbhs_priv_to_lock(priv));
/* call pipe and module init */
ret = usbhs_pipe_probe(priv);
if (ret < 0)
goto probe_end_mod_exit;
ret = usbhs_mod_probe(priv);
if (ret < 0)
goto probe_end_iounmap;
/* dev_set_drvdata should be called after usbhs_mod_init */
dev_set_drvdata(&pdev->dev, priv);
/*
* deviece reset here because
* USB device might be used in boot loader.
*/
usbhs_sys_clock_ctrl(priv, 0);
/*
* platform call
*
* USB phy setup might depend on CPU/Board.
* If platform has its callback functions,
* call it here.
*/
ret = usbhs_platform_call(priv, hardware_init, pdev);
if (ret < 0) {
dev_err(&pdev->dev, "platform prove failed.\n");
goto probe_end_pipe_exit;
}
/* reset phy for connection */
usbhs_platform_call(priv, phy_reset, pdev);
/*
* manual call notify_hotplug for cold plug
*/
pm_runtime_enable(&pdev->dev);
ret = usbhsc_drvcllbck_notify_hotplug(pdev);
if (ret < 0)
goto probe_end_call_remove;
dev_info(&pdev->dev, "probed\n");
return ret;
probe_end_call_remove:
usbhs_platform_call(priv, hardware_exit, pdev);
probe_end_pipe_exit:
usbhs_pipe_remove(priv);
probe_end_mod_exit:
usbhs_mod_remove(priv);
probe_end_iounmap:
iounmap(priv->base);
probe_end_kfree:
kfree(priv);
dev_info(&pdev->dev, "probe failed\n");
return ret;
}
static int __devexit usbhs_remove(struct platform_device *pdev)
{
struct usbhs_priv *priv = usbhsc_pdev_to_priv(pdev);
dev_dbg(&pdev->dev, "usb remove\n");
pm_runtime_disable(&pdev->dev);
usbhsc_bus_ctrl(priv, 0);
usbhs_platform_call(priv, hardware_exit, pdev);
usbhs_pipe_remove(priv);
usbhs_mod_remove(priv);
iounmap(priv->base);
kfree(priv);
return 0;
}
static struct platform_driver renesas_usbhs_driver = {
.driver = {
.name = "renesas_usbhs",
},
.probe = usbhs_probe,
.remove = __devexit_p(usbhs_remove),
};
static int __init usbhs_init(void)
{
return platform_driver_register(&renesas_usbhs_driver);
}
static void __exit usbhs_exit(void)
{
platform_driver_unregister(&renesas_usbhs_driver);
}
module_init(usbhs_init);
module_exit(usbhs_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Renesas USB driver");
MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
/*
* Renesas USB driver
*
* Copyright (C) 2011 Renesas Solutions Corp.
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef RENESAS_USB_DRIVER_H
#define RENESAS_USB_DRIVER_H
#include <linux/platform_device.h>
#include <linux/usb/renesas_usbhs.h>
struct usbhs_priv;
#include "./mod.h"
#include "./pipe.h"
/*
*
* register define
*
*/
#define SYSCFG 0x0000
#define BUSWAIT 0x0002
#define DVSTCTR 0x0008
#define CFIFO 0x0014
#define CFIFOSEL 0x0020
#define CFIFOCTR 0x0022
#define INTENB0 0x0030
#define INTENB1 0x0032
#define BRDYENB 0x0036
#define NRDYENB 0x0038
#define BEMPENB 0x003A
#define INTSTS0 0x0040
#define INTSTS1 0x0042
#define BRDYSTS 0x0046
#define NRDYSTS 0x0048
#define BEMPSTS 0x004A
#define FRMNUM 0x004C
#define USBREQ 0x0054 /* USB request type register */
#define USBVAL 0x0056 /* USB request value register */
#define USBINDX 0x0058 /* USB request index register */
#define USBLENG 0x005A /* USB request length register */
#define DCPCFG 0x005C
#define DCPMAXP 0x005E
#define DCPCTR 0x0060
#define PIPESEL 0x0064
#define PIPECFG 0x0068
#define PIPEBUF 0x006A
#define PIPEMAXP 0x006C
#define PIPEPERI 0x006E
#define PIPEnCTR 0x0070
/* SYSCFG */
#define SCKE (1 << 10) /* USB Module Clock Enable */
#define HSE (1 << 7) /* High-Speed Operation Enable */
#define DCFM (1 << 6) /* Controller Function Select */
#define DRPD (1 << 5) /* D+ Line/D- Line Resistance Control */
#define DPRPU (1 << 4) /* D+ Line Resistance Control */
#define USBE (1 << 0) /* USB Module Operation Enable */
/* DVSTCTR */
#define EXTLP (1 << 10) /* Controls the EXTLP pin output state */
#define PWEN (1 << 9) /* Controls the PWEN pin output state */
#define RHST (0x7) /* Reset Handshake */
#define RHST_LOW_SPEED 1 /* Low-speed connection */
#define RHST_FULL_SPEED 2 /* Full-speed connection */
#define RHST_HIGH_SPEED 3 /* High-speed connection */
/* CFIFOSEL */
#define MBW_32 (0x2 << 10) /* CFIFO Port Access Bit Width */
/* CFIFOCTR */
#define BVAL (1 << 15) /* Buffer Memory Enable Flag */
#define BCLR (1 << 14) /* CPU buffer clear */
#define FRDY (1 << 13) /* FIFO Port Ready */
#define DTLN_MASK (0x0FFF) /* Receive Data Length */
/* INTENB0 */
#define VBSE (1 << 15) /* Enable IRQ VBUS_0 and VBUSIN_0 */
#define RSME (1 << 14) /* Enable IRQ Resume */
#define SOFE (1 << 13) /* Enable IRQ Frame Number Update */
#define DVSE (1 << 12) /* Enable IRQ Device State Transition */
#define CTRE (1 << 11) /* Enable IRQ Control Stage Transition */
#define BEMPE (1 << 10) /* Enable IRQ Buffer Empty */
#define NRDYE (1 << 9) /* Enable IRQ Buffer Not Ready Response */
#define BRDYE (1 << 8) /* Enable IRQ Buffer Ready */
/* INTENB1 */
#define BCHGE (1 << 14) /* USB Bus Change Interrupt Enable */
#define DTCHE (1 << 12) /* Disconnection Detect Interrupt Enable */
#define ATTCHE (1 << 11) /* Connection Detect Interrupt Enable */
#define EOFERRE (1 << 6) /* EOF Error Detect Interrupt Enable */
#define SIGNE (1 << 5) /* Setup Transaction Error Interrupt Enable */
#define SACKE (1 << 4) /* Setup Transaction ACK Interrupt Enable */
/* INTSTS0 */
#define DVST (1 << 12) /* Device State Transition Interrupt Status */
#define CTRT (1 << 11) /* Control Stage Interrupt Status */
#define BEMP (1 << 10) /* Buffer Empty Interrupt Status */
#define BRDY (1 << 8) /* Buffer Ready Interrupt Status */
#define VBSTS (1 << 7) /* VBUS_0 and VBUSIN_0 Input Status */
#define VALID (1 << 3) /* USB Request Receive */
#define DVSQ_MASK (0x3 << 4) /* Device State */
#define POWER_STATE (0 << 4)
#define DEFAULT_STATE (1 << 4)
#define ADDRESS_STATE (2 << 4)
#define CONFIGURATION_STATE (3 << 4)
#define CTSQ_MASK (0x7) /* Control Transfer Stage */
#define IDLE_SETUP_STAGE 0 /* Idle stage or setup stage */
#define READ_DATA_STAGE 1 /* Control read data stage */
#define READ_STATUS_STAGE 2 /* Control read status stage */
#define WRITE_DATA_STAGE 3 /* Control write data stage */
#define WRITE_STATUS_STAGE 4 /* Control write status stage */
#define NODATA_STATUS_STAGE 5 /* Control write NoData status stage */
#define SEQUENCE_ERROR 6 /* Control transfer sequence error */
/* PIPECFG */
/* DCPCFG */
#define TYPE_NONE (0 << 14) /* Transfer Type */
#define TYPE_BULK (1 << 14)
#define TYPE_INT (2 << 14)
#define TYPE_ISO (3 << 14)
#define DBLB (1 << 9) /* Double Buffer Mode */
#define SHTNAK (1 << 7) /* Pipe Disable in Transfer End */
#define DIR_OUT (1 << 4) /* Transfer Direction */
/* PIPEMAXP */
/* DCPMAXP */
#define DEVSEL_MASK (0xF << 12) /* Device Select */
#define DCP_MAXP_MASK (0x7F)
#define PIPE_MAXP_MASK (0x7FF)
/* PIPEBUF */
#define BUFSIZE_SHIFT 10
#define BUFSIZE_MASK (0x1F << BUFSIZE_SHIFT)
#define BUFNMB_MASK (0xFF)
/* PIPEnCTR */
/* DCPCTR */
#define BSTS (1 << 15) /* Buffer Status */
#define CSSTS (1 << 12) /* CSSTS Status */
#define SQCLR (1 << 8) /* Toggle Bit Clear */
#define ACLRM (1 << 9) /* Buffer Auto-Clear Mode */
#define PBUSY (1 << 5) /* Pipe Busy */
#define PID_MASK (0x3) /* Response PID */
#define PID_NAK 0
#define PID_BUF 1
#define PID_STALL10 2
#define PID_STALL11 3
#define CCPL (1 << 2) /* Control Transfer End Enable */
/* FRMNUM */
#define FRNM_MASK (0x7FF)
/*
* struct
*/
struct usbhs_priv {
void __iomem *base;
unsigned int irq;
struct renesas_usbhs_platform_callback *pfunc;
struct renesas_usbhs_driver_param *dparam;
struct work_struct notify_hotplug_work;
struct platform_device *pdev;
spinlock_t lock;
/*
* module control
*/
struct usbhs_mod_info mod_info;
/*
* pipe control
*/
struct usbhs_pipe_info pipe_info;
};
/*
* common
*/
u16 usbhs_read(struct usbhs_priv *priv, u32 reg);
void usbhs_write(struct usbhs_priv *priv, u32 reg, u16 data);
void usbhs_bset(struct usbhs_priv *priv, u32 reg, u16 mask, u16 data);
/*
* sysconfig
*/
void usbhs_sys_clock_ctrl(struct usbhs_priv *priv, int enable);
void usbhs_sys_hispeed_ctrl(struct usbhs_priv *priv, int enable);
void usbhs_sys_usb_ctrl(struct usbhs_priv *priv, int enable);
void usbhs_sys_host_ctrl(struct usbhs_priv *priv, int enable);
void usbhs_sys_function_ctrl(struct usbhs_priv *priv, int enable);
/*
* frame
*/
int usbhs_frame_get_num(struct usbhs_priv *priv);
/*
* data
*/
#define usbhs_get_dparam(priv, param) (priv->dparam->param)
#define usbhs_priv_to_pdev(priv) (priv->pdev)
#define usbhs_priv_to_dev(priv) (&priv->pdev->dev)
#define usbhs_priv_to_lock(priv) (&priv->lock)
#endif /* RENESAS_USB_DRIVER_H */
/*
* Renesas USB driver
*
* Copyright (C) 2011 Renesas Solutions Corp.
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <linux/interrupt.h>
#include "./common.h"
#include "./mod.h"
#define usbhs_priv_to_modinfo(priv) (&priv->mod_info)
/*
* host / gadget functions
*
* renesas_usbhs host/gadget can register itself by below functions.
* these functions are called when probe
*
*/
void usbhs_mod_register(struct usbhs_priv *priv, struct usbhs_mod *mod, int id)
{
struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
info->mod[id] = mod;
mod->priv = priv;
}
struct usbhs_mod *usbhs_mod_get(struct usbhs_priv *priv, int id)
{
struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
struct usbhs_mod *ret = NULL;
switch (id) {
case USBHS_HOST:
case USBHS_GADGET:
ret = info->mod[id];
break;
}
return ret;
}
int usbhs_mod_is_host(struct usbhs_priv *priv, struct usbhs_mod *mod)
{
struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
if (!mod)
return -EINVAL;
return info->mod[USBHS_HOST] == mod;
}
struct usbhs_mod *usbhs_mod_get_current(struct usbhs_priv *priv)
{
struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
return info->curt;
}
int usbhs_mod_change(struct usbhs_priv *priv, int id)
{
struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
struct usbhs_mod *mod = NULL;
int ret = 0;
/* id < 0 mean no current */
switch (id) {
case USBHS_HOST:
case USBHS_GADGET:
mod = info->mod[id];
break;
default:
ret = -EINVAL;
}
info->curt = mod;
return ret;
}
static irqreturn_t usbhs_interrupt(int irq, void *data);
int usbhs_mod_probe(struct usbhs_priv *priv)
{
struct device *dev = usbhs_priv_to_dev(priv);
int ret;
/* irq settings */
ret = request_irq(priv->irq, usbhs_interrupt,
IRQF_DISABLED, dev_name(dev), priv);
if (ret)
dev_err(dev, "irq request err\n");
return ret;
}
void usbhs_mod_remove(struct usbhs_priv *priv)
{
free_irq(priv->irq, priv);
}
/*
* status functions
*/
int usbhs_status_get_usb_speed(struct usbhs_irq_state *irq_state)
{
switch (irq_state->dvstctr & RHST) {
case RHST_LOW_SPEED:
return USB_SPEED_LOW;
case RHST_FULL_SPEED:
return USB_SPEED_FULL;
case RHST_HIGH_SPEED:
return USB_SPEED_HIGH;
}
return USB_SPEED_UNKNOWN;
}
int usbhs_status_get_device_state(struct usbhs_irq_state *irq_state)
{
int state = irq_state->intsts0 & DVSQ_MASK;
switch (state) {
case POWER_STATE:
case DEFAULT_STATE:
case ADDRESS_STATE:
case CONFIGURATION_STATE:
return state;
}
return -EIO;
}
int usbhs_status_get_ctrl_stage(struct usbhs_irq_state *irq_state)
{
/*
* return value
*
* IDLE_SETUP_STAGE
* READ_DATA_STAGE
* READ_STATUS_STAGE
* WRITE_DATA_STAGE
* WRITE_STATUS_STAGE
* NODATA_STATUS_STAGE
* SEQUENCE_ERROR
*/
return (int)irq_state->intsts0 & CTSQ_MASK;
}
static void usbhs_status_get_each_irq(struct usbhs_priv *priv,
struct usbhs_irq_state *state)
{
struct usbhs_mod *mod = usbhs_mod_get_current(priv);
state->intsts0 = usbhs_read(priv, INTSTS0);
state->intsts1 = usbhs_read(priv, INTSTS1);
state->brdysts = usbhs_read(priv, BRDYSTS);
state->nrdysts = usbhs_read(priv, NRDYSTS);
state->bempsts = usbhs_read(priv, BEMPSTS);
state->dvstctr = usbhs_read(priv, DVSTCTR);
/* mask */
state->bempsts &= mod->irq_bempsts;
state->brdysts &= mod->irq_brdysts;
}
/*
* interrupt
*/
#define INTSTS0_MAGIC 0xF800 /* acknowledge magical interrupt sources */
#define INTSTS1_MAGIC 0xA870 /* acknowledge magical interrupt sources */
static irqreturn_t usbhs_interrupt(int irq, void *data)
{
struct usbhs_priv *priv = data;
struct usbhs_irq_state irq_state;
usbhs_status_get_each_irq(priv, &irq_state);
/*
* clear interrupt
*
* The hardware is _very_ picky to clear interrupt bit.
* Especially INTSTS0_MAGIC, INTSTS1_MAGIC value.
*
* see
* "Operation"
* - "Control Transfer (DCP)"
* - Function :: VALID bit should 0
*/
usbhs_write(priv, INTSTS0, ~irq_state.intsts0 & INTSTS0_MAGIC);
usbhs_write(priv, INTSTS1, ~irq_state.intsts1 & INTSTS1_MAGIC);
usbhs_write(priv, BRDYSTS, 0);
usbhs_write(priv, NRDYSTS, 0);
usbhs_write(priv, BEMPSTS, 0);
/*
* call irq callback functions
* see also
* usbhs_irq_setting_update
*/
if (irq_state.intsts0 & DVST)
usbhs_mod_call(priv, irq_dev_state, priv, &irq_state);
if (irq_state.intsts0 & CTRT)
usbhs_mod_call(priv, irq_ctrl_stage, priv, &irq_state);
if (irq_state.intsts0 & BEMP)
usbhs_mod_call(priv, irq_empty, priv, &irq_state);
if (irq_state.intsts0 & BRDY)
usbhs_mod_call(priv, irq_ready, priv, &irq_state);
return IRQ_HANDLED;
}
void usbhs_irq_callback_update(struct usbhs_priv *priv, struct usbhs_mod *mod)
{
u16 intenb0 = 0;
usbhs_write(priv, INTENB0, 0);
usbhs_write(priv, BEMPENB, 0);
usbhs_write(priv, BRDYENB, 0);
/*
* see also
* usbhs_interrupt
*/
/*
* it don't enable DVSE (intenb0) here
* but "mod->irq_dev_state" will be called.
*/
if (mod->irq_ctrl_stage)
intenb0 |= CTRE;
if (mod->irq_empty && mod->irq_bempsts) {
usbhs_write(priv, BEMPENB, mod->irq_bempsts);
intenb0 |= BEMPE;
}
if (mod->irq_ready && mod->irq_brdysts) {
usbhs_write(priv, BRDYENB, mod->irq_brdysts);
intenb0 |= BRDYE;
}
usbhs_write(priv, INTENB0, intenb0);
}
/*
* Renesas USB driver
*
* Copyright (C) 2011 Renesas Solutions Corp.
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef RENESAS_USB_MOD_H
#define RENESAS_USB_MOD_H
#include <linux/spinlock.h>
#include <linux/usb/renesas_usbhs.h>
#include "./common.h"
/*
* struct
*/
struct usbhs_irq_state {
u16 intsts0;
u16 intsts1;
u16 brdysts;
u16 nrdysts;
u16 bempsts;
u16 dvstctr;
};
struct usbhs_mod {
char *name;
/*
* entry point from common.c
*/
int (*start)(struct usbhs_priv *priv);
int (*stop)(struct usbhs_priv *priv);
/* INTSTS0 :: DVST (DVSQ) */
int (*irq_dev_state)(struct usbhs_priv *priv,
struct usbhs_irq_state *irq_state);
/* INTSTS0 :: CTRT (CTSQ) */
int (*irq_ctrl_stage)(struct usbhs_priv *priv,
struct usbhs_irq_state *irq_state);
/* INTSTS0 :: BEMP */
/* BEMPSTS */
int (*irq_empty)(struct usbhs_priv *priv,
struct usbhs_irq_state *irq_state);
u16 irq_bempsts;
/* INTSTS0 :: BRDY */
/* BRDYSTS */
int (*irq_ready)(struct usbhs_priv *priv,
struct usbhs_irq_state *irq_state);
u16 irq_brdysts;
struct usbhs_priv *priv;
};
struct usbhs_mod_info {
struct usbhs_mod *mod[USBHS_MAX];
struct usbhs_mod *curt; /* current mod */
};
/*
* for host/gadget module
*/
struct usbhs_mod *usbhs_mod_get(struct usbhs_priv *priv, int id);
struct usbhs_mod *usbhs_mod_get_current(struct usbhs_priv *priv);
void usbhs_mod_register(struct usbhs_priv *priv, struct usbhs_mod *usb, int id);
int usbhs_mod_is_host(struct usbhs_priv *priv, struct usbhs_mod *mod);
int usbhs_mod_change(struct usbhs_priv *priv, int id);
int usbhs_mod_probe(struct usbhs_priv *priv);
void usbhs_mod_remove(struct usbhs_priv *priv);
/*
* status functions
*/
int usbhs_status_get_usb_speed(struct usbhs_irq_state *irq_state);
int usbhs_status_get_device_state(struct usbhs_irq_state *irq_state);
int usbhs_status_get_ctrl_stage(struct usbhs_irq_state *irq_state);
/*
* callback functions
*/
void usbhs_irq_callback_update(struct usbhs_priv *priv, struct usbhs_mod *mod);
#define usbhs_mod_call(priv, func, param...) \
({ \
struct usbhs_mod *mod; \
mod = usbhs_mod_get_current(priv); \
!mod ? -ENODEV : \
!mod->func ? 0 : \
mod->func(param); \
})
#endif /* RENESAS_USB_MOD_H */
/*
* Renesas USB driver
*
* Copyright (C) 2011 Renesas Solutions Corp.
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/slab.h>
#include "./common.h"
#include "./pipe.h"
/*
* macros
*/
#define usbhsp_priv_to_pipeinfo(pr) (&(pr)->pipe_info)
#define usbhsp_pipe_to_priv(p) ((p)->priv)
#define usbhsp_addr_offset(p) ((usbhs_pipe_number(p) - 1) * 2)
#define usbhsp_is_dcp(p) ((p)->priv->pipe_info.pipe == (p))
#define usbhsp_flags_set(p, f) ((p)->flags |= USBHS_PIPE_FLAGS_##f)
#define usbhsp_flags_clr(p, f) ((p)->flags &= ~USBHS_PIPE_FLAGS_##f)
#define usbhsp_flags_has(p, f) ((p)->flags & USBHS_PIPE_FLAGS_##f)
#define usbhsp_flags_init(p) do {(p)->flags = 0; } while (0)
#define usbhsp_type(p) ((p)->pipe_type)
#define usbhsp_type_is(p, t) ((p)->pipe_type == t)
/*
* for debug
*/
static char *usbhsp_pipe_name[] = {
[USB_ENDPOINT_XFER_CONTROL] = "DCP",
[USB_ENDPOINT_XFER_BULK] = "BULK",
[USB_ENDPOINT_XFER_INT] = "INT",
[USB_ENDPOINT_XFER_ISOC] = "ISO",
};
/*
* usb request functions
*/
void usbhs_usbreq_get_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req)
{
u16 val;
val = usbhs_read(priv, USBREQ);
req->bRequest = (val >> 8) & 0xFF;
req->bRequestType = (val >> 0) & 0xFF;
req->wValue = usbhs_read(priv, USBVAL);
req->wIndex = usbhs_read(priv, USBINDX);
req->wLength = usbhs_read(priv, USBLENG);
}
void usbhs_usbreq_set_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req)
{
usbhs_write(priv, USBREQ, (req->bRequest << 8) | req->bRequestType);
usbhs_write(priv, USBVAL, req->wValue);
usbhs_write(priv, USBINDX, req->wIndex);
usbhs_write(priv, USBLENG, req->wLength);
}
/*
* DCPCTR/PIPEnCTR functions
*/
static void usbhsp_pipectrl_set(struct usbhs_pipe *pipe, u16 mask, u16 val)
{
struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe);
int offset = usbhsp_addr_offset(pipe);
if (usbhsp_is_dcp(pipe))
usbhs_bset(priv, DCPCTR, mask, val);
else
usbhs_bset(priv, PIPEnCTR + offset, mask, val);
}
static u16 usbhsp_pipectrl_get(struct usbhs_pipe *pipe)
{
struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe);
int offset = usbhsp_addr_offset(pipe);
if (usbhsp_is_dcp(pipe))
return usbhs_read(priv, DCPCTR);
else
return usbhs_read(priv, PIPEnCTR + offset);
}
/*
* DCP/PIPE functions
*/
static void __usbhsp_pipe_xxx_set(struct usbhs_pipe *pipe,
u16 dcp_reg, u16 pipe_reg,
u16 mask, u16 val)
{
struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe);
if (usbhsp_is_dcp(pipe))
usbhs_bset(priv, dcp_reg, mask, val);
else
usbhs_bset(priv, pipe_reg, mask, val);
}
static u16 __usbhsp_pipe_xxx_get(struct usbhs_pipe *pipe,
u16 dcp_reg, u16 pipe_reg)
{
struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe);
if (usbhsp_is_dcp(pipe))
return usbhs_read(priv, dcp_reg);
else
return usbhs_read(priv, pipe_reg);
}
/*
* DCPCFG/PIPECFG functions
*/
static void usbhsp_pipe_cfg_set(struct usbhs_pipe *pipe, u16 mask, u16 val)
{
__usbhsp_pipe_xxx_set(pipe, DCPCFG, PIPECFG, mask, val);
}
/*
* PIPEBUF
*/
static void usbhsp_pipe_buf_set(struct usbhs_pipe *pipe, u16 mask, u16 val)
{
if (usbhsp_is_dcp(pipe))
return;
__usbhsp_pipe_xxx_set(pipe, 0, PIPEBUF, mask, val);
}
/*
* DCPMAXP/PIPEMAXP
*/
static void usbhsp_pipe_maxp_set(struct usbhs_pipe *pipe, u16 mask, u16 val)
{
__usbhsp_pipe_xxx_set(pipe, DCPMAXP, PIPEMAXP, mask, val);
}
static u16 usbhsp_pipe_maxp_get(struct usbhs_pipe *pipe)
{
return __usbhsp_pipe_xxx_get(pipe, DCPMAXP, PIPEMAXP);
}
/*
* pipe control functions
*/
static void usbhsp_pipe_select(struct usbhs_pipe *pipe)
{
struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe);
/*
* On pipe, this is necessary before
* accesses to below registers.
*
* PIPESEL : usbhsp_pipe_select
* PIPECFG : usbhsp_pipe_cfg_xxx
* PIPEBUF : usbhsp_pipe_buf_xxx
* PIPEMAXP : usbhsp_pipe_maxp_xxx
* PIPEPERI
*/
/*
* if pipe is dcp, no pipe is selected.
* it is no problem, because dcp have its register
*/
usbhs_write(priv, PIPESEL, 0xF & usbhs_pipe_number(pipe));
}
static int usbhsp_pipe_barrier(struct usbhs_pipe *pipe)
{
struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe);
struct device *dev = usbhs_priv_to_dev(priv);
int timeout = 1024;
u16 val;
/*
* make sure....
*
* Modify these bits when CSSTS = 0, PID = NAK, and no pipe number is
* specified by the CURPIPE bits.
* When changing the setting of this bit after changing
* the PID bits for the selected pipe from BUF to NAK,
* check that CSSTS = 0 and PBUSY = 0.
*/
/*
* CURPIPE bit = 0
*
* see also
* "Operation"
* - "Pipe Control"
* - "Pipe Control Registers Switching Procedure"
*/
usbhs_write(priv, CFIFOSEL, 0);
do {
val = usbhsp_pipectrl_get(pipe);
val &= CSSTS | PID_MASK;
if (!val)
return 0;
udelay(10);
} while (timeout--);
/*
* force NAK
*/
timeout = 1024;
usbhs_fifo_disable(pipe);
do {
val = usbhsp_pipectrl_get(pipe);
val &= PBUSY;
if (!val)
return 0;
} while (timeout--);
dev_err(dev, "pipe barrier failed\n");
return -EBUSY;
}
static int usbhsp_pipe_is_accessible(struct usbhs_pipe *pipe)
{
u16 val;
val = usbhsp_pipectrl_get(pipe);
if (val & BSTS)
return 0;
return -EBUSY;
}
/*
* PID ctrl
*/
static void __usbhsp_pid_try_nak_if_stall(struct usbhs_pipe *pipe)
{
u16 pid = usbhsp_pipectrl_get(pipe);
pid &= PID_MASK;
/*
* see
* "Pipe n Control Register" - "PID"
*/
switch (pid) {
case PID_STALL11:
usbhsp_pipectrl_set(pipe, PID_MASK, PID_STALL10);
/* fall-through */
case PID_STALL10:
usbhsp_pipectrl_set(pipe, PID_MASK, PID_NAK);
}
}
void usbhs_fifo_disable(struct usbhs_pipe *pipe)
{
/* see "Pipe n Control Register" - "PID" */
__usbhsp_pid_try_nak_if_stall(pipe);
usbhsp_pipectrl_set(pipe, PID_MASK, PID_NAK);
}
void usbhs_fifo_enable(struct usbhs_pipe *pipe)
{
/* see "Pipe n Control Register" - "PID" */
__usbhsp_pid_try_nak_if_stall(pipe);
usbhsp_pipectrl_set(pipe, PID_MASK, PID_BUF);
}
void usbhs_fifo_stall(struct usbhs_pipe *pipe)
{
u16 pid = usbhsp_pipectrl_get(pipe);
pid &= PID_MASK;
/*
* see
* "Pipe n Control Register" - "PID"
*/
switch (pid) {
case PID_NAK:
usbhsp_pipectrl_set(pipe, PID_MASK, PID_STALL10);
break;
case PID_BUF:
usbhsp_pipectrl_set(pipe, PID_MASK, PID_STALL11);
break;
}
}
/*
* CFIFO ctrl
*/
void usbhs_fifo_send_terminator(struct usbhs_pipe *pipe)
{
struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe);
usbhs_bset(priv, CFIFOCTR, BVAL, BVAL);
}
static void usbhsp_fifo_clear(struct usbhs_pipe *pipe)
{
struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe);
usbhs_write(priv, CFIFOCTR, BCLR);
}
static int usbhsp_fifo_barrier(struct usbhs_priv *priv)
{
int timeout = 1024;
do {
/* The FIFO port is accessible */
if (usbhs_read(priv, CFIFOCTR) & FRDY)
return 0;
udelay(10);
} while (timeout--);
return -EBUSY;
}
static int usbhsp_fifo_rcv_len(struct usbhs_priv *priv)
{
return usbhs_read(priv, CFIFOCTR) & DTLN_MASK;
}
static int usbhsp_fifo_select(struct usbhs_pipe *pipe, int write)
{
struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe);
struct device *dev = usbhs_priv_to_dev(priv);
int timeout = 1024;
u16 mask = ((1 << 5) | 0xF); /* mask of ISEL | CURPIPE */
u16 base = usbhs_pipe_number(pipe); /* CURPIPE */
if (usbhsp_is_dcp(pipe))
base |= (1 == write) << 5; /* ISEL */
/* "base" will be used below */
usbhs_write(priv, CFIFOSEL, base | MBW_32);
/* check ISEL and CURPIPE value */
while (timeout--) {
if (base == (mask & usbhs_read(priv, CFIFOSEL)))
return 0;
udelay(10);
}
dev_err(dev, "fifo select error\n");
return -EIO;
}
int usbhs_fifo_prepare_write(struct usbhs_pipe *pipe)
{
int ret;
ret = usbhsp_fifo_select(pipe, 1);
if (ret < 0)
return ret;
usbhsp_fifo_clear(pipe);
return ret;
}
int usbhs_fifo_write(struct usbhs_pipe *pipe, u8 *buf, int len)
{
struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe);
void __iomem *addr = priv->base + CFIFO;
int maxp = usbhs_pipe_get_maxpacket(pipe);
int total_len;
int i, ret;
ret = usbhsp_pipe_is_accessible(pipe);
if (ret < 0)
return ret;
ret = usbhs_fifo_prepare_write(pipe);
if (ret < 0)
return ret;
ret = usbhsp_fifo_barrier(priv);
if (ret < 0)
return ret;
len = min(len, maxp);
total_len = len;
/*
* FIXME
*
* 32-bit access only
*/
if (len >= 4 &&
!((unsigned long)buf & 0x03)) {
iowrite32_rep(addr, buf, len / 4);
len %= 4;
buf += total_len - len;
}
/* the rest operation */
for (i = 0; i < len; i++)
iowrite8(buf[i], addr + (0x03 - (i & 0x03)));
if (total_len < maxp)
usbhs_fifo_send_terminator(pipe);
return total_len;
}
int usbhs_fifo_prepare_read(struct usbhs_pipe *pipe)
{
int ret;
/*
* select pipe and enable it to prepare packet receive
*/
ret = usbhsp_fifo_select(pipe, 0);
if (ret < 0)
return ret;
usbhs_fifo_enable(pipe);
return ret;
}
int usbhs_fifo_read(struct usbhs_pipe *pipe, u8 *buf, int len)
{
struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe);
void __iomem *addr = priv->base + CFIFO;
int rcv_len;
int i, ret;
int total_len;
u32 data = 0;
ret = usbhsp_fifo_select(pipe, 0);
if (ret < 0)
return ret;
ret = usbhsp_fifo_barrier(priv);
if (ret < 0)
return ret;
rcv_len = usbhsp_fifo_rcv_len(priv);
/*
* Buffer clear if Zero-Length packet
*
* see
* "Operation" - "FIFO Buffer Memory" - "FIFO Port Function"
*/
if (0 == rcv_len) {
usbhsp_fifo_clear(pipe);
return 0;
}
len = min(rcv_len, len);
total_len = len;
/*
* FIXME
*
* 32-bit access only
*/
if (len >= 4 &&
!((unsigned long)buf & 0x03)) {
ioread32_rep(addr, buf, len / 4);
len %= 4;
buf += rcv_len - len;
}
/* the rest operation */
for (i = 0; i < len; i++) {
if (!(i & 0x03))
data = ioread32(addr);
buf[i] = (data >> ((i & 0x03) * 8)) & 0xff;
}
return total_len;
}
/*
* pipe setup
*/
static int usbhsp_possible_double_buffer(struct usbhs_pipe *pipe)
{
/*
* only ISO / BULK pipe can use double buffer
*/
if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_BULK) ||
usbhsp_type_is(pipe, USB_ENDPOINT_XFER_ISOC))
return 1;
return 0;
}
static u16 usbhsp_setup_pipecfg(struct usbhs_pipe *pipe,
const struct usb_endpoint_descriptor *desc,
int is_host)
{
u16 type = 0;
u16 bfre = 0;
u16 dblb = 0;
u16 cntmd = 0;
u16 dir = 0;
u16 epnum = 0;
u16 shtnak = 0;
u16 type_array[] = {
[USB_ENDPOINT_XFER_BULK] = TYPE_BULK,
[USB_ENDPOINT_XFER_INT] = TYPE_INT,
[USB_ENDPOINT_XFER_ISOC] = TYPE_ISO,
};
int is_double = usbhsp_possible_double_buffer(pipe);
if (usbhsp_is_dcp(pipe))
return -EINVAL;
/*
* PIPECFG
*
* see
* - "Register Descriptions" - "PIPECFG" register
* - "Features" - "Pipe configuration"
* - "Operation" - "Pipe Control"
*/
/* TYPE */
type = type_array[usbhsp_type(pipe)];
/* BFRE */
if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_ISOC) ||
usbhsp_type_is(pipe, USB_ENDPOINT_XFER_BULK))
bfre = 0; /* FIXME */
/* DBLB */
if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_ISOC) ||
usbhsp_type_is(pipe, USB_ENDPOINT_XFER_BULK))
dblb = (is_double) ? DBLB : 0;
/* CNTMD */
if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_BULK))
cntmd = 0; /* FIXME */
/* DIR */
if (usb_endpoint_dir_in(desc))
usbhsp_flags_set(pipe, IS_DIR_IN);
if ((is_host && usb_endpoint_dir_out(desc)) ||
(!is_host && usb_endpoint_dir_in(desc)))
dir |= DIR_OUT;
/* SHTNAK */
if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_BULK) &&
!dir)
shtnak = SHTNAK;
/* EPNUM */
epnum = 0xF & usb_endpoint_num(desc);
return type |
bfre |
dblb |
cntmd |
dir |
shtnak |
epnum;
}
static u16 usbhsp_setup_pipemaxp(struct usbhs_pipe *pipe,
const struct usb_endpoint_descriptor *desc,
int is_host)
{
/* host should set DEVSEL */
/* reutn MXPS */
return PIPE_MAXP_MASK & le16_to_cpu(desc->wMaxPacketSize);
}
static u16 usbhsp_setup_pipebuff(struct usbhs_pipe *pipe,
const struct usb_endpoint_descriptor *desc,
int is_host)
{
struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe);
struct usbhs_pipe_info *info = usbhsp_priv_to_pipeinfo(priv);
struct device *dev = usbhs_priv_to_dev(priv);
int pipe_num = usbhs_pipe_number(pipe);
int is_double = usbhsp_possible_double_buffer(pipe);
u16 buff_size;
u16 bufnmb;
u16 bufnmb_cnt;
/*
* PIPEBUF
*
* see
* - "Register Descriptions" - "PIPEBUF" register
* - "Features" - "Pipe configuration"
* - "Operation" - "FIFO Buffer Memory"
* - "Operation" - "Pipe Control"
*
* ex) if pipe6 - pipe9 are USB_ENDPOINT_XFER_INT (SH7724)
*
* BUFNMB: PIPE
* 0: pipe0 (DCP 256byte)
* 1: -
* 2: -
* 3: -
* 4: pipe6 (INT 64byte)
* 5: pipe7 (INT 64byte)
* 6: pipe8 (INT 64byte)
* 7: pipe9 (INT 64byte)
* 8 - xx: free (for BULK, ISOC)
*/
/*
* FIXME
*
* it doesn't have good buffer allocator
*
* DCP : 256 byte
* BULK: 512 byte
* INT : 64 byte
* ISOC: 512 byte
*/
if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_CONTROL))
buff_size = 256;
else if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_INT))
buff_size = 64;
else
buff_size = 512;
/* change buff_size to register value */
bufnmb_cnt = (buff_size / 64) - 1;
/* BUFNMB has been reserved for INT pipe
* see above */
if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_INT)) {
bufnmb = pipe_num - 2;
} else {
bufnmb = info->bufnmb_last;
info->bufnmb_last += bufnmb_cnt + 1;
/*
* double buffer
*/
if (is_double)
info->bufnmb_last += bufnmb_cnt + 1;
}
dev_dbg(dev, "pipe : %d : buff_size 0x%x: bufnmb 0x%x\n",
pipe_num, buff_size, bufnmb);
return (0x1f & bufnmb_cnt) << 10 |
(0xff & bufnmb) << 0;
}
/*
* pipe control
*/
int usbhs_pipe_get_maxpacket(struct usbhs_pipe *pipe)
{
u16 mask = usbhsp_is_dcp(pipe) ? DCP_MAXP_MASK : PIPE_MAXP_MASK;
usbhsp_pipe_select(pipe);
return (int)(usbhsp_pipe_maxp_get(pipe) & mask);
}
int usbhs_pipe_is_dir_in(struct usbhs_pipe *pipe)
{
return usbhsp_flags_has(pipe, IS_DIR_IN);
}
void usbhs_pipe_clear_sequence(struct usbhs_pipe *pipe)
{
usbhsp_pipectrl_set(pipe, SQCLR, SQCLR);
}
static struct usbhs_pipe *usbhsp_get_pipe(struct usbhs_priv *priv, u32 type)
{
struct usbhs_pipe *pos, *pipe;
int i;
/*
* find target pipe
*/
pipe = NULL;
usbhs_for_each_pipe_with_dcp(pos, priv, i) {
if (!usbhsp_type_is(pos, type))
continue;
if (usbhsp_flags_has(pos, IS_USED))
continue;
pipe = pos;
break;
}
if (!pipe)
return NULL;
/*
* initialize pipe flags
*/
usbhsp_flags_init(pipe);
usbhsp_flags_set(pipe, IS_USED);
return pipe;
}
void usbhs_pipe_init(struct usbhs_priv *priv)
{
struct usbhs_pipe_info *info = usbhsp_priv_to_pipeinfo(priv);
struct usbhs_pipe *pipe;
int i;
/*
* FIXME
*
* driver needs good allocator.
*
* find first free buffer area (BULK, ISOC)
* (DCP, INT area is fixed)
*
* buffer number 0 - 3 have been reserved for DCP
* see
* usbhsp_to_bufnmb
*/
info->bufnmb_last = 4;
usbhs_for_each_pipe_with_dcp(pipe, priv, i) {
if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_INT))
info->bufnmb_last++;
usbhsp_flags_init(pipe);
pipe->mod_private = NULL;
}
}
struct usbhs_pipe *usbhs_pipe_malloc(struct usbhs_priv *priv,
const struct usb_endpoint_descriptor *desc)
{
struct device *dev = usbhs_priv_to_dev(priv);
struct usbhs_mod *mod = usbhs_mod_get_current(priv);
struct usbhs_pipe *pipe;
int is_host = usbhs_mod_is_host(priv, mod);
int ret;
u16 pipecfg, pipebuf, pipemaxp;
pipe = usbhsp_get_pipe(priv, usb_endpoint_type(desc));
if (!pipe)
return NULL;
usbhs_fifo_disable(pipe);
/* make sure pipe is not busy */
ret = usbhsp_pipe_barrier(pipe);
if (ret < 0) {
dev_err(dev, "pipe setup failed %d\n", usbhs_pipe_number(pipe));
return NULL;
}
pipecfg = usbhsp_setup_pipecfg(pipe, desc, is_host);
pipebuf = usbhsp_setup_pipebuff(pipe, desc, is_host);
pipemaxp = usbhsp_setup_pipemaxp(pipe, desc, is_host);
/* buffer clear
* see PIPECFG :: BFRE */
usbhsp_pipectrl_set(pipe, ACLRM, ACLRM);
usbhsp_pipectrl_set(pipe, ACLRM, 0);
usbhsp_pipe_select(pipe);
usbhsp_pipe_cfg_set(pipe, 0xFFFF, pipecfg);
usbhsp_pipe_buf_set(pipe, 0xFFFF, pipebuf);
usbhsp_pipe_maxp_set(pipe, 0xFFFF, pipemaxp);
usbhs_pipe_clear_sequence(pipe);
dev_dbg(dev, "enable pipe %d : %s (%s)\n",
usbhs_pipe_number(pipe),
usbhsp_pipe_name[usb_endpoint_type(desc)],
usbhs_pipe_is_dir_in(pipe) ? "in" : "out");
return pipe;
}
/*
* dcp control
*/
struct usbhs_pipe *usbhs_dcp_malloc(struct usbhs_priv *priv)
{
struct usbhs_pipe *pipe;
pipe = usbhsp_get_pipe(priv, USB_ENDPOINT_XFER_CONTROL);
if (!pipe)
return NULL;
/*
* dcpcfg : default
* dcpmaxp : default
* pipebuf : nothing to do
*/
usbhsp_pipe_select(pipe);
usbhs_pipe_clear_sequence(pipe);
return pipe;
}
void usbhs_dcp_control_transfer_done(struct usbhs_pipe *pipe)
{
WARN_ON(!usbhsp_is_dcp(pipe));
usbhs_fifo_enable(pipe);
usbhsp_pipectrl_set(pipe, CCPL, CCPL);
}
/*
* pipe module function
*/
int usbhs_pipe_probe(struct usbhs_priv *priv)
{
struct usbhs_pipe_info *info = usbhsp_priv_to_pipeinfo(priv);
struct usbhs_pipe *pipe;
struct device *dev = usbhs_priv_to_dev(priv);
u32 *pipe_type = usbhs_get_dparam(priv, pipe_type);
int pipe_size = usbhs_get_dparam(priv, pipe_size);
int i;
/* This driver expects 1st pipe is DCP */
if (pipe_type[0] != USB_ENDPOINT_XFER_CONTROL) {
dev_err(dev, "1st PIPE is not DCP\n");
return -EINVAL;
}
info->pipe = kzalloc(sizeof(struct usbhs_pipe) * pipe_size, GFP_KERNEL);
if (!info->pipe) {
dev_err(dev, "Could not allocate pipe\n");
return -ENOMEM;
}
info->size = pipe_size;
/*
* init pipe
*/
usbhs_for_each_pipe_with_dcp(pipe, priv, i) {
pipe->priv = priv;
usbhsp_type(pipe) = pipe_type[i] & USB_ENDPOINT_XFERTYPE_MASK;
dev_dbg(dev, "pipe %x\t: %s\n",
i, usbhsp_pipe_name[pipe_type[i]]);
}
return 0;
}
void usbhs_pipe_remove(struct usbhs_priv *priv)
{
struct usbhs_pipe_info *info = usbhsp_priv_to_pipeinfo(priv);
kfree(info->pipe);
}
/*
* Renesas USB driver
*
* Copyright (C) 2011 Renesas Solutions Corp.
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef RENESAS_USB_PIPE_H
#define RENESAS_USB_PIPE_H
#include "./common.h"
/*
* struct
*/
struct usbhs_pipe {
u32 pipe_type; /* USB_ENDPOINT_XFER_xxx */
struct usbhs_priv *priv;
u32 flags;
#define USBHS_PIPE_FLAGS_IS_USED (1 << 0)
#define USBHS_PIPE_FLAGS_IS_DIR_IN (1 << 1)
void *mod_private;
};
struct usbhs_pipe_info {
struct usbhs_pipe *pipe;
int size; /* array size of "pipe" */
int bufnmb_last; /* FIXME : driver needs good allocator */
};
/*
* pipe list
*/
#define __usbhs_for_each_pipe(start, pos, info, i) \
for (i = start, pos = (info)->pipe; \
i < (info)->size; \
i++, pos = (info)->pipe + i)
#define usbhs_for_each_pipe(pos, priv, i) \
__usbhs_for_each_pipe(1, pos, &((priv)->pipe_info), i)
#define usbhs_for_each_pipe_with_dcp(pos, priv, i) \
__usbhs_for_each_pipe(0, pos, &((priv)->pipe_info), i)
/*
* pipe module probe / remove
*/
int usbhs_pipe_probe(struct usbhs_priv *priv);
void usbhs_pipe_remove(struct usbhs_priv *priv);
/*
* cfifo
*/
int usbhs_fifo_write(struct usbhs_pipe *pipe, u8 *buf, int len);
int usbhs_fifo_read(struct usbhs_pipe *pipe, u8 *buf, int len);
int usbhs_fifo_prepare_write(struct usbhs_pipe *pipe);
int usbhs_fifo_prepare_read(struct usbhs_pipe *pipe);
void usbhs_fifo_enable(struct usbhs_pipe *pipe);
void usbhs_fifo_disable(struct usbhs_pipe *pipe);
void usbhs_fifo_stall(struct usbhs_pipe *pipe);
void usbhs_fifo_send_terminator(struct usbhs_pipe *pipe);
/*
* usb request
*/
void usbhs_usbreq_get_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req);
void usbhs_usbreq_set_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req);
/*
* pipe control
*/
struct usbhs_pipe
*usbhs_pipe_malloc(struct usbhs_priv *priv,
const struct usb_endpoint_descriptor *desc);
int usbhs_pipe_is_dir_in(struct usbhs_pipe *pipe);
void usbhs_pipe_init(struct usbhs_priv *priv);
int usbhs_pipe_get_maxpacket(struct usbhs_pipe *pipe);
void usbhs_pipe_clear_sequence(struct usbhs_pipe *pipe);
#define usbhs_pipe_number(p) (((u32)(p) - (u32)(p)->priv->pipe_info.pipe) / \
sizeof(struct usbhs_pipe))
/*
* dcp control
*/
struct usbhs_pipe *usbhs_dcp_malloc(struct usbhs_priv *priv);
void usbhs_dcp_control_transfer_done(struct usbhs_pipe *pipe);
#endif /* RENESAS_USB_PIPE_H */
/*
* Renesas USB
*
* Copyright (C) 2011 Renesas Solutions Corp.
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef RENESAS_USB_H
#define RENESAS_USB_H
#include <linux/platform_device.h>
#include <linux/usb/ch9.h>
/*
* module type
*
* it will be return value from get_id
*/
enum {
USBHS_HOST = 0,
USBHS_GADGET,
USBHS_MAX,
};
/*
* callback functions table for driver
*
* These functions are called from platform for driver.
* Callback function's pointer will be set before
* renesas_usbhs_platform_callback :: hardware_init was called
*/
struct renesas_usbhs_driver_callback {
int (*notify_hotplug)(struct platform_device *pdev);
};
/*
* callback functions for platform
*
* These functions are called from driver for platform
*/
struct renesas_usbhs_platform_callback {
/*
* option:
*
* Hardware init function for platform.
* it is called when driver was probed.
*/
int (*hardware_init)(struct platform_device *pdev);
/*
* option:
*
* Hardware exit function for platform.
* it is called when driver was removed
*/
void (*hardware_exit)(struct platform_device *pdev);
/*
* option:
*
* Phy reset for platform
*/
void (*phy_reset)(struct platform_device *pdev);
/*
* get USB ID function
* - USBHS_HOST
* - USBHS_GADGET
*/
int (*get_id)(struct platform_device *pdev);
/*
* get VBUS status function.
*/
int (*get_vbus)(struct platform_device *pdev);
};
/*
* parameters for renesas usbhs
*
* some register needs USB chip specific parameters.
* This struct show it to driver
*/
struct renesas_usbhs_driver_param {
/*
* pipe settings
*/
u32 *pipe_type; /* array of USB_ENDPOINT_XFER_xxx (from ep0) */
int pipe_size; /* pipe_type array size */
/*
* option:
*
* for BUSWAIT :: BWAIT
* */
int buswait_bwait;
};
/*
* option:
*
* platform information for renesas_usbhs driver.
*/
struct renesas_usbhs_platform_info {
/*
* option:
*
* platform set these functions before
* call platform_add_devices if needed
*/
struct renesas_usbhs_platform_callback platform_callback;
/*
* driver set these callback functions pointer.
* platform can use it on callback functions
*/
struct renesas_usbhs_driver_callback driver_callback;
/*
* option:
*
* driver use these param for some register
*/
struct renesas_usbhs_driver_param driver_param;
};
/*
* macro for platform
*/
#define renesas_usbhs_get_info(pdev)\
((struct renesas_usbhs_platform_info *)(pdev)->dev.platform_data)
#define renesas_usbhs_call_notify_hotplug(pdev) \
({ \
struct renesas_usbhs_driver_callback *dc; \
dc = &(renesas_usbhs_get_info(pdev)->driver_callback); \
if (dc) \
dc->notify_hotplug(pdev); \
})
#endif /* RENESAS_USB_H */
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