9
0
Fork 0
barebox/drivers/usb/musb/musb_dsps.c

465 lines
11 KiB
C

/*
* Texas Instruments DSPS platforms "glue layer"
*
* Copyright (C) 2012, by Texas Instruments
*
* Based on the am35x "glue layer" code.
*
* This file is part of the Inventra Controller Driver for Linux.
*
* The Inventra Controller Driver for Linux is free software; you
* can redistribute it and/or modify it under the terms of the GNU
* General Public License version 2 as published by the Free Software
* Foundation.
*
* The Inventra Controller Driver for Linux 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 The Inventra Controller Driver for Linux ; if not,
* write to the Free Software Foundation, Inc., 59 Temple Place,
* Suite 330, Boston, MA 02111-1307 USA
*
* musb_dsps.c will be a common file for all the TI DSPS platforms
* such as dm64x, dm36x, dm35x, da8x, am35x and ti81x.
* For now only ti81x is using this and in future davinci.c, am35x.c
* da8xx.c would be merged to this file after testing.
*/
#include <common.h>
#include <init.h>
#include <clock.h>
#include <usb/usb.h>
#include <usb/musb.h>
#include <malloc.h>
#include <linux/err.h>
#include <linux/barebox-wrapper.h>
#include "musb_core.h"
#include "phy-am335x.h"
static __maybe_unused struct of_device_id musb_dsps_dt_ids[];
/**
* avoid using musb_readx()/musb_writex() as glue layer should not be
* dependent on musb core layer symbols.
*/
static inline u8 dsps_readb(const void __iomem *addr, unsigned offset)
{
return __raw_readb(addr + offset);
}
static inline u32 dsps_readl(const void __iomem *addr, unsigned offset)
{
return __raw_readl(addr + offset);
}
static inline void dsps_writeb(void __iomem *addr, unsigned offset, u8 data)
{
__raw_writeb(data, addr + offset);
}
static inline void dsps_writel(void __iomem *addr, unsigned offset, u32 data)
{
__raw_writel(data, addr + offset);
}
/**
* DSPS musb wrapper register offset.
* FIXME: This should be expanded to have all the wrapper registers from TI DSPS
* musb ips.
*/
struct dsps_musb_wrapper {
u16 revision;
u16 control;
u16 status;
u16 epintr_set;
u16 epintr_clear;
u16 epintr_status;
u16 coreintr_set;
u16 coreintr_clear;
u16 coreintr_status;
u16 phy_utmi;
u16 mode;
/* bit positions for control */
unsigned reset:5;
/* bit positions for interrupt */
unsigned usb_shift:5;
u32 usb_mask;
u32 usb_bitmap;
unsigned drvvbus:5;
unsigned txep_shift:5;
u32 txep_mask;
u32 txep_bitmap;
unsigned rxep_shift:5;
u32 rxep_mask;
u32 rxep_bitmap;
/* bit positions for phy_utmi */
unsigned otg_disable:5;
/* bit positions for mode */
unsigned iddig:5;
/* miscellaneous stuff */
u8 poll_seconds;
};
/**
* DSPS glue structure.
*/
struct dsps_glue {
struct device_d *dev;
void __iomem *base;
unsigned long flags;
enum musb_mode mode;
struct musb musb;
struct musb_hdrc_config config;
const struct dsps_musb_wrapper *wrp; /* wrapper register offsets */
struct poller_async timer; /* otg_workaround timer */
uint64_t last_timer; /* last timer data for each instance */
struct device_d otg_dev;
uint32_t otgmode;
struct musb_hdrc_platform_data pdata;
};
/**
* dsps_musb_enable - enable interrupts
*/
static void dsps_musb_enable(struct musb *musb)
{
struct device_d *dev = musb->controller;
struct device_d *pdev = dev;
struct dsps_glue *glue = pdev->priv;
const struct dsps_musb_wrapper *wrp = glue->wrp;
void __iomem *reg_base = musb->ctrl_base;
u32 epmask, coremask;
/* Workaround: setup IRQs through both register sets. */
epmask = ((musb->epmask & wrp->txep_mask) << wrp->txep_shift) |
((musb->epmask & wrp->rxep_mask) << wrp->rxep_shift);
coremask = (wrp->usb_bitmap & ~MUSB_INTR_SOF);
dsps_writel(reg_base, wrp->epintr_set, epmask);
dsps_writel(reg_base, wrp->coreintr_set, coremask);
/* Force the DRVVBUS IRQ so we can start polling for ID change. */
dsps_writel(reg_base, wrp->coreintr_set,
(1 << wrp->drvvbus) << wrp->usb_shift);
}
/**
* dsps_musb_disable - disable HDRC and flush interrupts
*/
static void dsps_musb_disable(struct musb *musb)
{
struct device_d *dev = musb->controller;
struct device_d *pdev = dev;
struct dsps_glue *glue = pdev->priv;
const struct dsps_musb_wrapper *wrp = glue->wrp;
void __iomem *reg_base = musb->ctrl_base;
dsps_writel(reg_base, wrp->coreintr_clear, wrp->usb_bitmap);
dsps_writel(reg_base, wrp->epintr_clear,
wrp->txep_bitmap | wrp->rxep_bitmap);
dsps_writeb(musb->mregs, MUSB_DEVCTL, 0);
}
static irqreturn_t dsps_interrupt(struct musb *musb)
{
void __iomem *reg_base = musb->ctrl_base;
struct device_d *dev = musb->controller;
struct dsps_glue *glue = dev->priv;
const struct dsps_musb_wrapper *wrp = glue->wrp;
unsigned long flags;
irqreturn_t ret = IRQ_NONE;
u32 epintr, usbintr;
spin_lock_irqsave(&musb->lock, flags);
/* Get endpoint interrupts */
epintr = dsps_readl(reg_base, wrp->epintr_status);
musb->int_rx = (epintr & wrp->rxep_bitmap) >> wrp->rxep_shift;
musb->int_tx = (epintr & wrp->txep_bitmap) >> wrp->txep_shift;
if (epintr)
dsps_writel(reg_base, wrp->epintr_status, epintr);
/* Get usb core interrupts */
usbintr = dsps_readl(reg_base, wrp->coreintr_status);
if (!usbintr && !epintr)
goto out;
musb->int_usb = (usbintr & wrp->usb_bitmap) >> wrp->usb_shift;
if (usbintr)
dsps_writel(reg_base, wrp->coreintr_status, usbintr);
dev_dbg(musb->controller, "usbintr (%x) epintr(%x)\n",
usbintr, epintr);
if (musb->int_tx || musb->int_rx || musb->int_usb)
ret |= musb_interrupt(musb);
out:
spin_unlock_irqrestore(&musb->lock, flags);
return ret;
}
static int dsps_musb_init(struct musb *musb)
{
struct device_d *dev = musb->controller;
struct dsps_glue *glue = dev->priv;
const struct dsps_musb_wrapper *wrp = glue->wrp;
u32 rev, val, mode;
musb->xceiv = am335x_get_usb_phy();
if (IS_ERR(musb->xceiv))
return PTR_ERR(musb->xceiv);
/* Returns zero if e.g. not clocked */
rev = dsps_readl(musb->ctrl_base, wrp->revision);
if (!rev)
return -ENODEV;
usb_phy_init(musb->xceiv);
/* Reset the musb */
dsps_writel(musb->ctrl_base, wrp->control, (1 << wrp->reset));
musb->isr = dsps_interrupt;
/* reset the otgdisable bit, needed for host mode to work */
val = dsps_readl(musb->ctrl_base, wrp->phy_utmi);
val &= ~(1 << wrp->otg_disable);
dsps_writel(musb->ctrl_base, wrp->phy_utmi, val);
mode = dsps_readl(musb->ctrl_base, wrp->mode);
switch (musb->port_mode) {
case MUSB_PORT_MODE_HOST:
mode &= ~0x100;
break;
case MUSB_PORT_MODE_GADGET:
mode |= 0x100;
break;
}
mode |= 0x80;
dsps_writel(musb->ctrl_base, wrp->mode, mode); /* IDDIG=0, IDDIG_MUX=1 */
return 0;
}
static int dsps_musb_exit(struct musb *musb)
{
usb_phy_shutdown(musb->xceiv);
return 0;
}
static struct musb_platform_ops dsps_ops = {
.init = dsps_musb_init,
.exit = dsps_musb_exit,
.enable = dsps_musb_enable,
.disable = dsps_musb_disable,
};
static int get_int_prop(struct device_node *dn, const char *s)
{
int ret;
u32 val;
ret = of_property_read_u32(dn, s, &val);
if (ret)
return 0;
return val;
}
static int get_musb_port_mode(struct device_d *dev)
{
enum usb_dr_mode mode;
mode = of_usb_get_dr_mode(dev->device_node, NULL);
switch (mode) {
case USB_DR_MODE_HOST:
return MUSB_PORT_MODE_HOST;
case USB_DR_MODE_PERIPHERAL:
return MUSB_PORT_MODE_GADGET;
case USB_DR_MODE_UNKNOWN:
case USB_DR_MODE_OTG:
default:
if (!IS_ENABLED(CONFIG_USB_MUSB_HOST))
return MUSB_PORT_MODE_GADGET;
if (!IS_ENABLED(CONFIG_USB_MUSB_GADGET))
return MUSB_PORT_MODE_HOST;
return MUSB_PORT_MODE_DUAL_ROLE;
}
}
static int dsps_set_mode(struct param_d *param, void *priv)
{
struct dsps_glue *glue = priv;
if (glue->pdata.mode != MUSB_PORT_MODE_DUAL_ROLE)
return -EBUSY;
switch (glue->otgmode) {
case 0:
default:
return -EINVAL;
case 1:
glue->pdata.mode = MUSB_PORT_MODE_HOST;
break;
case 2:
glue->pdata.mode = MUSB_PORT_MODE_GADGET;
break;
}
return musb_init_controller(&glue->musb, &glue->pdata);
}
static const char *dsps_mode_names[] = {
"otg", "host", "peripheral"
};
static int dsps_register_otg_device(struct dsps_glue *glue)
{
int ret;
strcpy(glue->otg_dev.name, "otg");
glue->otg_dev.id = DEVICE_ID_DYNAMIC,
glue->otg_dev.parent = glue->dev;
ret = register_device(&glue->otg_dev);
if (ret)
return ret;
dev_add_param_enum(&glue->otg_dev, "mode",
dsps_set_mode, NULL, &glue->otgmode,
dsps_mode_names, ARRAY_SIZE(dsps_mode_names), glue);
return 0;
}
static int dsps_probe(struct device_d *dev)
{
struct musb_hdrc_platform_data *pdata;
struct musb_hdrc_config *config;
struct device_node *dn = dev->device_node;
const struct dsps_musb_wrapper *wrp;
struct dsps_glue *glue;
int ret;
ret = dev_get_drvdata(dev, (const void **)&wrp);
if (ret)
return ret;
if (!IS_ENABLED(CONFIG_USB_MUSB_HOST) &&
!IS_ENABLED(CONFIG_USB_MUSB_GADGET)) {
dev_err(dev, "Both host and device driver disabled.\n");
return -ENODEV;
}
/* allocate glue */
glue = kzalloc(sizeof(*glue), GFP_KERNEL);
if (!glue) {
dev_err(dev, "unable to allocate glue memory\n");
return -ENOMEM;
}
glue->dev = dev;
glue->wrp = wrp;
dev->priv = glue;
pdata = &glue->pdata;
glue->musb.mregs = dev_request_mem_region(dev, 0);
if (IS_ERR(glue->musb.mregs))
return PTR_ERR(glue->musb.mregs);
glue->musb.ctrl_base = dev_request_mem_region(dev, 1);
if (IS_ERR(glue->musb.ctrl_base))
return PTR_ERR(glue->musb.ctrl_base);
glue->musb.controller = dev;
config = &glue->config;
pdata->config = config;
pdata->platform_ops = &dsps_ops;
config->num_eps = get_int_prop(dn, "mentor,num-eps");
config->ram_bits = get_int_prop(dn, "mentor,ram-bits");
pdata->mode = get_musb_port_mode(dev);
/* DT keeps this entry in mA, musb expects it as per USB spec */
pdata->power = get_int_prop(dn, "mentor,power") / 2;
config->multipoint = of_property_read_bool(dn, "mentor,multipoint");
if (pdata->mode == MUSB_PORT_MODE_DUAL_ROLE) {
ret = dsps_register_otg_device(glue);
if (ret)
return ret;
return 0;
}
return musb_init_controller(&glue->musb, pdata);
}
static const struct dsps_musb_wrapper am33xx_driver_data = {
.revision = 0x00,
.control = 0x14,
.status = 0x18,
.epintr_set = 0x38,
.epintr_clear = 0x40,
.epintr_status = 0x30,
.coreintr_set = 0x3c,
.coreintr_clear = 0x44,
.coreintr_status = 0x34,
.phy_utmi = 0xe0,
.mode = 0xe8,
.reset = 0,
.otg_disable = 21,
.iddig = 8,
.usb_shift = 0,
.usb_mask = 0x1ff,
.usb_bitmap = (0x1ff << 0),
.drvvbus = 8,
.txep_shift = 0,
.txep_mask = 0xffff,
.txep_bitmap = (0xffff << 0),
.rxep_shift = 16,
.rxep_mask = 0xfffe,
.rxep_bitmap = (0xfffe << 16),
.poll_seconds = 2,
};
static __maybe_unused struct of_device_id musb_dsps_dt_ids[] = {
{
.compatible = "ti,musb-am33xx",
.data = &am33xx_driver_data,
}, {
/* sentinel */
},
};
static struct driver_d dsps_usbss_driver = {
.name = "musb-dsps",
.probe = dsps_probe,
.of_compatible = DRV_OF_COMPAT(musb_dsps_dt_ids),
};
device_platform_driver(dsps_usbss_driver);
MODULE_DESCRIPTION("TI DSPS MUSB Glue Layer");
MODULE_AUTHOR("Ravi B <ravibabu@ti.com>");
MODULE_AUTHOR("Ajay Kumar Gupta <ajay.gupta@ti.com>");
MODULE_LICENSE("GPL v2");