2
0
mirror of https://github.com/edk2-porting/linux-next.git synced 2024-12-25 21:54:06 +08:00
linux-next/drivers/usb/renesas_usbhs/mod.c
Kuninori Morimoto 2f98382dcd usb: renesas_usbhs: Add Renesas USBHS Gadget
This patch add usb gadget code to SuperH USBHS.

Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2011-04-13 16:07:07 -07:00

277 lines
5.8 KiB
C

/*
* 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;
/*
* install host/gadget driver
*/
ret = usbhs_mod_gadget_probe(priv);
if (ret < 0)
return 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");
goto mod_init_gadget_err;
}
return ret;
mod_init_gadget_err:
usbhs_mod_gadget_remove(priv);
return ret;
}
void usbhs_mod_remove(struct usbhs_priv *priv)
{
usbhs_mod_gadget_remove(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);
}