forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
387 lines
8.4 KiB
387 lines
8.4 KiB
// SPDX-License-Identifier: GPL-1.0+ |
|
/* |
|
* Renesas USB driver |
|
* |
|
* Copyright (C) 2011 Renesas Solutions Corp. |
|
* Copyright (C) 2019 Renesas Electronics Corporation |
|
* Kuninori Morimoto <[email protected]> |
|
*/ |
|
#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); |
|
}
|
|
|