linux/drivers/usb/renesas_usbhs/mod.c
Eugeniu Rosca fef2263688 usb: renesas_usbhs: enable DVSE interrupt
Commit [1] enabled the possibility of checking the DVST (Device State
Transition) bit of INTSTS0 (Interrupt Status Register 0) and calling
the irq_dev_state() handler if the DVST bit is set. But neither
commit [1] nor commit [2] actually enabled the DVSE (Device State
Transition Interrupt Enable) bit in the INTENB0 (Interrupt Enable
Register 0). As a consequence, irq_dev_state() handler is getting
called as a side effect of other (non-DVSE) interrupts being fired,
which definitely can't be relied upon, if DVST notifications are of
any value.

Why this doesn't hurt is because usbhsg_irq_dev_state() currently
doesn't do much except of a dev_dbg(). Once more work is added to
the handler (e.g. detecting device "Suspended" state and notifying
other USB gadget components about it), enabling DVSE becomes a hard
requirement. Do it in a standalone commit for better visibility and
clear explanation.

[1] commit f1407d5c66 ("usb: renesas_usbhs: Add Renesas USBHS common
   code")
[2] commit 2f98382dcd ("usb: renesas_usbhs: Add Renesas USBHS Gadget")

Signed-off-by: Eugeniu Rosca <erosca@de.adit-jv.com>
Signed-off-by: Veeraiyan Chidambaram <veeraiyan.chidambaram@in.bosch.com>
Reviewed-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Link: https://lore.kernel.org/r/1568207756-22325-2-git-send-email-external.veeraiyan.c@de.adit-jv.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-10-04 14:16:54 +02:00

388 lines
8.4 KiB
C

// SPDX-License-Identifier: GPL-1.0+
/*
* Renesas USB driver
*
* Copyright (C) 2011 Renesas Solutions Corp.
* Copyright (C) 2019 Renesas Electronics Corporation
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
*/
#include <linux/interrupt.h>
#include "common.h"
#include "mod.h"
/*
* 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);
usbhsc_schedule_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;
info->get_vbus = usbhsm_autonomy_get_vbus;
usbhs_irq_callback_update(priv, NULL);
}
void usbhs_mod_non_autonomy_mode(struct usbhs_priv *priv)
{
struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
info->get_vbus = priv->pfunc->get_vbus;
}
/*
* 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)
{
return (int)irq_state->intsts0 & DVSQ_MASK;
}
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
*/
if (info->irq_vbus)
intenb0 |= VBSE;
if (mod) {
/*
* INTSTS0
*/
if (mod->irq_ctrl_stage)
intenb0 |= CTRE;
if (mod->irq_dev_state)
intenb0 |= DVSE;
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);
}