|  | // 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); | 
|  | } |