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: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
parent
a6360dd37e
commit
f1407d5c66
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
#
|
||||
# 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.
|
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# for Renesas USB
|
||||
#
|
||||
|
||||
obj-$(CONFIG_USB_RENESAS_USBHS) += renesas_usbhs.o
|
||||
|
||||
renesas_usbhs-y := common.o mod.o pipe.o
|
|
@ -0,0 +1,394 @@
|
|||
/*
|
||||
* 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>");
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* 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 */
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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 */
|
|
@ -0,0 +1,880 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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 */
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* 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 */
|
Loading…
Reference in New Issue