forked from Minki/linux
1250413a81
Now that the SPDX tag is in all USB files, that identifies the license in a specific and legally-defined manner. So the extra GPL text wording can be removed as it is no longer needed at all. This is done on a quest to remove the 700+ different ways that files in the kernel describe the GPL license text. And there's unneeded stuff like the address (sometimes incorrect) for the FSF which is never needed. No copyright headers or other non-license-description text was removed. Cc: Rob Herring <robh@kernel.org> Cc: Simon Horman <horms+renesas@verge.net.au> Cc: Geert Uytterhoeven <geert+renesas@glider.be> Cc: Chanwoo Choi <cw00.choi@samsung.com> Cc: Johan Hovold <johan@kernel.org> Cc: Kazuya Mizuguchi <kazuya.mizuguchi.ks@renesas.com> Cc: Bhumika Goyal <bhumirks@gmail.com> Acked-by: Felipe Balbi <felipe.balbi@linux.intel.com> Acked-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
400 lines
8.6 KiB
C
400 lines
8.6 KiB
C
// SPDX-License-Identifier: GPL-1.0+
|
|
/*
|
|
* Renesas USB driver
|
|
*
|
|
* Copyright (C) 2011 Renesas Solutions Corp.
|
|
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
|
|
*/
|
|
#include <linux/interrupt.h>
|
|
|
|
#include "common.h"
|
|
#include "mod.h"
|
|
|
|
#define usbhs_priv_to_modinfo(priv) (&priv->mod_info)
|
|
#define usbhs_mod_info_call(priv, func, param...) \
|
|
({ \
|
|
struct usbhs_mod_info *info; \
|
|
info = usbhs_priv_to_modinfo(priv); \
|
|
!info->func ? 0 : \
|
|
info->func(param); \
|
|
})
|
|
|
|
/*
|
|
* autonomy
|
|
*
|
|
* these functions are used if platform doesn't have external phy.
|
|
* -> there is no "notify_hotplug" callback from platform
|
|
* -> call "notify_hotplug" by itself
|
|
* -> use own interrupt to connect/disconnect
|
|
* -> it mean module clock is always ON
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
*/
|
|
static int usbhsm_autonomy_get_vbus(struct platform_device *pdev)
|
|
{
|
|
struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev);
|
|
|
|
return VBSTS & usbhs_read(priv, INTSTS0);
|
|
}
|
|
|
|
static int usbhsm_autonomy_irq_vbus(struct usbhs_priv *priv,
|
|
struct usbhs_irq_state *irq_state)
|
|
{
|
|
struct platform_device *pdev = usbhs_priv_to_pdev(priv);
|
|
|
|
renesas_usbhs_call_notify_hotplug(pdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void usbhs_mod_autonomy_mode(struct usbhs_priv *priv)
|
|
{
|
|
struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
|
|
|
|
info->irq_vbus = usbhsm_autonomy_irq_vbus;
|
|
priv->pfunc.get_vbus = usbhsm_autonomy_get_vbus;
|
|
|
|
usbhs_irq_callback_update(priv, NULL);
|
|
}
|
|
|
|
/*
|
|
* 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 = usbhs_mod_get_current(priv);
|
|
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_host_probe(priv);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = usbhs_mod_gadget_probe(priv);
|
|
if (ret < 0)
|
|
goto mod_init_host_err;
|
|
|
|
/* irq settings */
|
|
ret = devm_request_irq(dev, priv->irq, usbhs_interrupt,
|
|
priv->irqflags, 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);
|
|
mod_init_host_err:
|
|
usbhs_mod_host_remove(priv);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void usbhs_mod_remove(struct usbhs_priv *priv)
|
|
{
|
|
usbhs_mod_host_remove(priv);
|
|
usbhs_mod_gadget_remove(priv);
|
|
}
|
|
|
|
/*
|
|
* status functions
|
|
*/
|
|
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 int usbhs_status_get_each_irq(struct usbhs_priv *priv,
|
|
struct usbhs_irq_state *state)
|
|
{
|
|
struct usbhs_mod *mod = usbhs_mod_get_current(priv);
|
|
u16 intenb0, intenb1;
|
|
unsigned long flags;
|
|
|
|
/******************** spin lock ********************/
|
|
usbhs_lock(priv, flags);
|
|
state->intsts0 = usbhs_read(priv, INTSTS0);
|
|
intenb0 = usbhs_read(priv, INTENB0);
|
|
|
|
if (usbhs_mod_is_host(priv)) {
|
|
state->intsts1 = usbhs_read(priv, INTSTS1);
|
|
intenb1 = usbhs_read(priv, INTENB1);
|
|
} else {
|
|
state->intsts1 = intenb1 = 0;
|
|
}
|
|
|
|
/* mask */
|
|
if (mod) {
|
|
state->brdysts = usbhs_read(priv, BRDYSTS);
|
|
state->nrdysts = usbhs_read(priv, NRDYSTS);
|
|
state->bempsts = usbhs_read(priv, BEMPSTS);
|
|
|
|
state->bempsts &= mod->irq_bempsts;
|
|
state->brdysts &= mod->irq_brdysts;
|
|
}
|
|
usbhs_unlock(priv, flags);
|
|
/******************** spin unlock ******************/
|
|
|
|
/*
|
|
* Check whether the irq enable registers and the irq status are set
|
|
* when IRQF_SHARED is set.
|
|
*/
|
|
if (priv->irqflags & IRQF_SHARED) {
|
|
if (!(intenb0 & state->intsts0) &&
|
|
!(intenb1 & state->intsts1) &&
|
|
!(state->bempsts) &&
|
|
!(state->brdysts))
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
if (usbhs_status_get_each_irq(priv, &irq_state) < 0)
|
|
return IRQ_NONE;
|
|
|
|
/*
|
|
* 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);
|
|
if (usbhs_mod_is_host(priv))
|
|
usbhs_write(priv, INTSTS1, ~irq_state.intsts1 & INTSTS1_MAGIC);
|
|
|
|
/*
|
|
* The driver should not clear the xxxSTS after the line of
|
|
* "call irq callback functions" because each "if" statement is
|
|
* possible to call the callback function for avoiding any side effects.
|
|
*/
|
|
if (irq_state.intsts0 & BRDY)
|
|
usbhs_write(priv, BRDYSTS, ~irq_state.brdysts);
|
|
usbhs_write(priv, NRDYSTS, ~irq_state.nrdysts);
|
|
if (irq_state.intsts0 & BEMP)
|
|
usbhs_write(priv, BEMPSTS, ~irq_state.bempsts);
|
|
|
|
/*
|
|
* call irq callback functions
|
|
* see also
|
|
* usbhs_irq_setting_update
|
|
*/
|
|
|
|
/* INTSTS0 */
|
|
if (irq_state.intsts0 & VBINT)
|
|
usbhs_mod_info_call(priv, irq_vbus, priv, &irq_state);
|
|
|
|
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);
|
|
|
|
if (usbhs_mod_is_host(priv)) {
|
|
/* INTSTS1 */
|
|
if (irq_state.intsts1 & ATTCH)
|
|
usbhs_mod_call(priv, irq_attch, priv, &irq_state);
|
|
|
|
if (irq_state.intsts1 & DTCH)
|
|
usbhs_mod_call(priv, irq_dtch, priv, &irq_state);
|
|
|
|
if (irq_state.intsts1 & SIGN)
|
|
usbhs_mod_call(priv, irq_sign, priv, &irq_state);
|
|
|
|
if (irq_state.intsts1 & SACK)
|
|
usbhs_mod_call(priv, irq_sack, priv, &irq_state);
|
|
}
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
void usbhs_irq_callback_update(struct usbhs_priv *priv, struct usbhs_mod *mod)
|
|
{
|
|
u16 intenb0 = 0;
|
|
u16 intenb1 = 0;
|
|
struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
|
|
|
|
/*
|
|
* BEMPENB/BRDYENB are picky.
|
|
* below method is required
|
|
*
|
|
* - clear INTSTS0
|
|
* - update BEMPENB/BRDYENB
|
|
* - update INTSTS0
|
|
*/
|
|
usbhs_write(priv, INTENB0, 0);
|
|
if (usbhs_mod_is_host(priv))
|
|
usbhs_write(priv, INTENB1, 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 (info->irq_vbus)
|
|
intenb0 |= VBSE;
|
|
|
|
if (mod) {
|
|
/*
|
|
* INTSTS0
|
|
*/
|
|
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;
|
|
}
|
|
|
|
if (usbhs_mod_is_host(priv)) {
|
|
/*
|
|
* INTSTS1
|
|
*/
|
|
if (mod->irq_attch)
|
|
intenb1 |= ATTCHE;
|
|
|
|
if (mod->irq_dtch)
|
|
intenb1 |= DTCHE;
|
|
|
|
if (mod->irq_sign)
|
|
intenb1 |= SIGNE;
|
|
|
|
if (mod->irq_sack)
|
|
intenb1 |= SACKE;
|
|
}
|
|
}
|
|
|
|
if (intenb0)
|
|
usbhs_write(priv, INTENB0, intenb0);
|
|
|
|
if (usbhs_mod_is_host(priv) && intenb1)
|
|
usbhs_write(priv, INTENB1, intenb1);
|
|
}
|