mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-27 04:54:41 +08:00
7fd94beeca
renesas_usbhs implements ->pullup() method, switches over to devm_request_irq(), adds support for DMA Engine and got a few miscelaneous cleanups. The NCM gadget got an endianness fix and the Ethernet gadget a frame size fix. We're finally removing the g_file_storage gadget and sticking to g_mass_storage and the new tcm_usb_gadget gadgets since that was a huge duplicaton of effort anyway. While removing g_file_storage, we also had to fix a bunch of defconfigs which were still pointing to the old gadget. There's a big series getting us closer to being able to introduce our configfs interface. The series converts functions into loadable modules which will, eventually, be registered to the configfs interface. Other than that there's the usual typo fixes and miscelaneous cleanups all over the place. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (GNU/Linux) iQIcBAABAgAGBQJQnXPMAAoJEIaOsuA1yqREkygQALIuhY6veRPZoZJltuADeAOV h7lBkuseJxvlJsbMLnjqP5tw4W/haE1deGR+ee1ZItkPrERCX1++jkQ6hmm7e00R mvr8rI+n3eBHSKUO89tUfCaz5UBsTl0cowPWdTwxRrV4VRJ1wVBw/oII9sfyss03 jDo+11DSjTGTB+Bz72p2NTkRiv9my2Kz+ihhqFR5VSl5FyoutG53RNKRmciJKGB+ i+RptOI+prdW1uOURHbie5FAI0xOBrE1Up2XdNiZ9blT6zcsK754Lc8erFJEZXX5 7s8Ys/HJZLQCF/fRt4WAw8e1lSPELD2xuDMqV+WKu93aXOiAWL1SbzqK3Y+PaUDg Red07jOxgPqgq0F1mAp3+0Rs1RnshSvKREtQhZqsttg7suXhDB0q7h61CX8uQbRA hBZh8eFexRjqOZxveeV+h4ATz00c2nlEa8cJscr5zLf4R/LSxJWT7LV5227BDkBV 9NUMA3dunDYZLqnxBv5lS2gQzmYO6G11wzdpgjnABL2WlM8Pv1lUDhY+erwvTRzd BM+9qMd7K40BuI1JyUsbBdmuEpJAD/yWE77pT2aBrr4767x0CYjBPZqQAxXFcWi8 5NG1BzqWmH9HhwxKyWueWgNgY253cRcAzFlUN80NRA2UuNkMAeOAeJjvK48isAqJ T1MUkQgIFNvSecpRPrEl =umtl -----END PGP SIGNATURE----- Merge tag 'gadget-for-v3.8' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb into usb-next USB gadget patches from Felipe: "usb: gadget: patches for v3.8 renesas_usbhs implements ->pullup() method, switches over to devm_request_irq(), adds support for DMA Engine and got a few miscelaneous cleanups. The NCM gadget got an endianness fix and the Ethernet gadget a frame size fix. We're finally removing the g_file_storage gadget and sticking to g_mass_storage and the new tcm_usb_gadget gadgets since that was a huge duplicaton of effort anyway. While removing g_file_storage, we also had to fix a bunch of defconfigs which were still pointing to the old gadget. There's a big series getting us closer to being able to introduce our configfs interface. The series converts functions into loadable modules which will, eventually, be registered to the configfs interface. Other than that there's the usual typo fixes and miscelaneous cleanups all over the place."
388 lines
8.3 KiB
C
388 lines
8.3 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)
|
|
#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;
|
|
|
|
state->intsts0 = usbhs_read(priv, INTSTS0);
|
|
state->intsts1 = usbhs_read(priv, INTSTS1);
|
|
|
|
intenb0 = usbhs_read(priv, INTENB0);
|
|
intenb1 = usbhs_read(priv, INTENB1);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
usbhs_write(priv, INTSTS1, ~irq_state.intsts1 & INTSTS1_MAGIC);
|
|
|
|
usbhs_write(priv, BRDYSTS, ~irq_state.brdysts);
|
|
usbhs_write(priv, NRDYSTS, ~irq_state.nrdysts);
|
|
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);
|
|
|
|
/* 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);
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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 (intenb1)
|
|
usbhs_write(priv, INTENB1, intenb1);
|
|
}
|