blob: cfe405897f8d577dbf00564b92e11d0c5a49ae97 [file] [log] [blame]
/*
* Driver for Special USB-PHY of MUSB HOST peripheral
*
* The power sequence and programming bits were different from SoC.
* Please use the coorsponding USB-PHY API for your SoC.
* This driver includes Mediatek MUSB DT/ICUSB support.
*
* Copyright 2015 Mediatek Inc.
* Marvin Lin <marvin.lin@mediatek.com>
* Arvin Wang <arvin.wang@mediatek.com>
* Vincent Fan <vincent.fan@mediatek.com>
* Bryant Lu <bryant.lu@mediatek.com>
* Yu-Chang Wang <yu-chang.wang@mediatek.com>
* Macpaul Lin <macpaul.lin@mediatek.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#ifdef CONFIG_OF
#include <linux/of_irq.h>
#include <linux/of_address.h>
#endif
#include "musbfsh_core.h"
#include "musbfsh_mt65xx.h"
#define FRA (48)
#define PARA (25)
bool musbfsh_power;
struct mt_usb11_glue {
struct device *dev;
struct platform_device *musbfsh;
};
static const struct of_device_id apusb_of_ids[] = {
{.compatible = "mediatek,mt8167-usb11",},
{},
};
void usb11_hs_slew_rate_cal(void)
{
unsigned long data;
unsigned long x;
unsigned char value;
unsigned long start_time, timeout;
unsigned int timeout_flag = 0;
/*4 s1:enable usb ring oscillator.*/
USB11PHY_WRITE8(0x15, 0x80);
/*4 s2:wait 1us.*/
udelay(1);
/*4 s3:enable free run clock*/
USB11PHY_WRITE8(0xf00 - 0x900 + 0x11, 0x01);
/*4 s4:setting cyclecnt*/
USB11PHY_WRITE8(0xf00 - 0x900 + 0x01, 0x04);
/*4 s5:enable frequency meter*/
USB11PHY_SET8(0xf00 - 0x900 + 0x03, 0x05);
/*4 s6:wait for frequency valid.*/
start_time = jiffies;
timeout = jiffies + 3 * HZ;
while (!(USB11PHY_READ8(0xf00 - 0x900 + 0x10) & 0x1)) {
if (time_after(jiffies, timeout)) {
timeout_flag = 1;
break;
}
}
/*4 s7: read result.*/
if (timeout_flag) {
INFO("[USBPHY] Slew Rate Calibration: Timeout\n");
value = 0x4;
} else {
data = USB11PHY_READ32(0xf00 - 0x900 + 0x0c);
x = ((1024 * FRA * PARA) / data);
value = (unsigned char)(x / 1000);
if (((x - value * 1000) / 100) >= 5)
value += 1;
/* INFO("[USB11PHY]slew calibration:FM_OUT =%d, x=%d,value=%d\n",data,x,value);*/
}
/*4 s8: disable Frequency and run clock.*/
USB11PHY_CLR8(0xf00 - 0x900 + 0x03, 0x05); /*disable frequency meter*/
USB11PHY_CLR8(0xf00 - 0x900 + 0x11, 0x01); /*disable free run clock*/
/*4 s9: */
USB11PHY_WRITE8(0x15, value << 4);
/*4 s10:disable usb ring oscillator.*/
USB11PHY_CLR8(0x15, 0x80);
}
void mt65xx_usb11_phy_poweron(void)
{
INFO("mt65xx_usb11_phy_poweron++\r\n");
INFO("[Flow][USB11]%s:%d\n", __func__, __LINE__);
/* enable_pll(UNIVPLL, "USB11"); */
/*udelay(100); */
udelay(50);
USB11PHY_CLR8(0x6b, 0x04);
USB11PHY_CLR8(0x6e, 0x01);
USB11PHY_CLR8(0x1a, 0x80);
/* remove in MT6588 ?????
* USBPHY_CLR8(0x02, 0x7f);
* USBPHY_SET8(0x02, 0x09);
* USBPHY_CLR8(0x22, 0x03);
*/
USB11PHY_CLR8(0x6a, 0x04);
/*USBPHY_SET8(0x1b, 0x08);*/
/*force VBUS Valid */
USB11PHY_SET8(0x6C, 0x2C);
USB11PHY_SET8(0x6D, 0x3C);
/* VBUSVALID=0, AVALID=0, BVALID=0, SESSEND=1, IDDIG=X */
USB11PHY_SET8(0x6c, 0x10);
USB11PHY_CLR8(0x6c, 0x2e);
USB11PHY_SET8(0x6d, 0x3e);
/* wait */
msleep(20);
/* restart session */
/* USB MAC ONand Host Mode */
/* VBUSVALID=1, AVALID=1, BVALID=1, SESSEND=0, IDDIG=0 */
USB11PHY_CLR8(0x6c, 0x10);
USB11PHY_SET8(0x6c, 0x2c);
USB11PHY_SET8(0x6d, 0x3e);
udelay(800);
}
void mt65xx_usb11_phy_savecurrent(void)
{
INFO("mt65xx_usb11_phy_savecurrent++\r\n");
INFO("[Flow][USB11]%s:%d\n", __func__, __LINE__);
/*4 1. swtich to USB function. (system register, force ip into usb mode.*/
USB11PHY_CLR8(0x6b, 0x04);
USB11PHY_CLR8(0x6e, 0x01);
/*4 2. release force suspendm.*/
USB11PHY_CLR8(0x6a, 0x04);
/*4 3. RG_DPPULLDOWN./RG_DMPULLDOWN.*/
USB11PHY_SET8(0x68, 0xc0);
/*4 4. RG_XCVRSEL[1:0] =2'b01.*/
USB11PHY_CLR8(0x68, 0x30);
USB11PHY_SET8(0x68, 0x10);
/*4 5. RG_TERMSEL = 1'b1*/
USB11PHY_SET8(0x68, 0x04);
/*4 6. RG_DATAIN[3:0]=4'b0000*/
USB11PHY_CLR8(0x69, 0x3c);
/*4 7.force_dp_pulldown, force_dm_pulldown, force_xcversel,force_termsel.*/
USB11PHY_SET8(0x6a, 0xba);
/*4 8.RG_USB20_BC11_SW_EN 1'b0*/
USB11PHY_CLR8(0x1a, 0x80);
/*4 9.RG_USB20_OTG_VBUSSCMP_EN 1'b0*/
USB11PHY_CLR8(0x1a, 0x10);
/*4 10. delay 800us.*/
udelay(800);
/*4 11. rg_usb20_pll_stable = 1*/
USB11PHY_SET8(0x63, 0x02);
udelay(1);
/*4 12. force suspendm = 1.*/
USB11PHY_SET8(0x6a, 0x04);
USB11PHY_CLR8(0x6C, 0x2C);
USB11PHY_SET8(0x6C, 0x10);
USB11PHY_CLR8(0x6D, 0x3C);
#if 0
/*4 13. wait 1us*/
udelay(1);
/*4 14. turn off internal 48Mhz PLL.*/
enable_phy_clock(false);
#endif
}
void mt81xx_usb11_phy_recover(void)
{
INFO("[Flow][USB11]%s:%d\n", __func__, __LINE__);
INFO("mt65xx_usb11_phy_recover++\r\n");
/*4 1. turn on USB reference clock. */
/*enable_pll(UNIVPLL, "USB11"); */
/*
* swtich to USB function.
* (system register, force ip into usb mode).
*/
USB11PHY_CLR8(0x6b, 0x04);
USB11PHY_CLR8(0x6e, 0x01);
/* RG_USB20_BC11_SW_EN = 1'b0 */
USB11PHY_CLR8(0x1a, 0x80);
/* RG_USB20_DP_100K_EN = 1'b0 */
/* RG_USB20_DM_100K_EN = 1'b0 */
USB11PHY_CLR8(0x22, 0x03);
/* release force suspendm */
USB11PHY_CLR8(0x6a, 0x04);
udelay(800);
/* force enter device mode */
USB11PHY_CLR8(0x6c, 0x10);
USB11PHY_SET8(0x6c, 0x2E);
USB11PHY_SET8(0x6d, 0x3E);
/* clean PUPD_BIST_EN */
/* PUPD_BIST_EN = 1'b0 */
/* PMIC will use it to detect charger type */
/*USB11PHY_CLR8(0x1d, 0x10);*/
/* force_uart_en = 1'b0 */
USB11PHY_CLR8(0x6b, 0x04);
/* RG_UART_EN = 1'b0 */
USB11PHY_CLR8(0x6e, 0x01);
/* force_uart_en = 1'b0 */
USB11PHY_CLR8(0x6a, 0x04);
USB11PHY_CLR8(0x68, 0xf4);
USB11PHY_SET8(0x68, 0x08);
/* RG_DATAIN[3:0] = 4'b0000 */
USB11PHY_CLR8(0x69, 0x3c);
USB11PHY_CLR8(0x6a, 0xba);
/* RG_USB20_BC11_SW_EN = 1'b0 */
USB11PHY_SET8(0x1a, 0x10);
USB11PHY_CLR8(0x1a, 0x80);
/* RG_USB20_OTG_VBUSSCMP_EN = 1'b1 */
USB11PHY_SET8(0x1a, 0x10);
udelay(800);
/* force enter device mode */
USB11PHY_CLR8(0x6c, 0x10);
USB11PHY_SET8(0x6c, 0x2E);
USB11PHY_SET8(0x6d, 0x3E);
/* force enter host mode */
udelay(100);
USB11PHY_SET8(0x6d, 0x3e);
USB11PHY_SET8(0x6c, 0x10);
USB11PHY_CLR8(0x6c, 0x2e);
udelay(5);
USB11PHY_CLR8(0x6c, 0x10);
USB11PHY_SET8(0x6c, 0x2c);
}
void mt65xx_usb11_phy_recover(void)
{
INFO("[Flow][USB11]%s:%d\n", __func__, __LINE__);
INFO("mt65xx_usb11_phy_recover++\r\n");
#if 0
/*4 1. turn on USB reference clock. */
enable_phy_clock(true);
#endif
INFO("[Flow][USB11]%s:%d\n", __func__, __LINE__);
USB11PHY_CLR8(0x6b, 0x04);
USB11PHY_CLR8(0x6e, 0x01);
/* RG_USB20_BC11_SW_EN = 1'b0 */
USB11PHY_CLR8(0x1a, 0x80);
/* RG_USB20_DP_100K_EN = 1'b0 */
/* RG_USB20_DM_100K_EN = 1'b0 */
USB11PHY_CLR8(0x22, 0x03);
/* release force suspendm */
USB11PHY_CLR8(0x6a, 0x04);
udelay(800);
/* force enter device mode */
USB11PHY_CLR8(0x6c, 0x10);
USB11PHY_SET8(0x6c, 0x2E);
USB11PHY_SET8(0x6d, 0x3E);
/* clean PUPD_BIST_EN */
/* PUPD_BIST_EN = 1'b0 */
/* PMIC will use it to detect charger type */
USB11PHY_CLR8(0x1d, 0x10);
/* force_uart_en = 1'b0 */
USB11PHY_CLR8(0x6b, 0x04);
/* RG_UART_EN = 1'b0 */
USB11PHY_CLR8(0x6e, 0x01);
/* force_uart_en = 1'b0 */
USB11PHY_CLR8(0x6a, 0x04);
USB11PHY_CLR8(0x68, 0xf4);
/* RG_DATAIN[3:0] = 4'b0000 */
USB11PHY_CLR8(0x69, 0x3c);
USB11PHY_CLR8(0x6a, 0xba);
/* RG_USB20_BC11_SW_EN = 1'b0 */
USB11PHY_CLR8(0x1a, 0x80);
/* RG_USB20_OTG_VBUSSCMP_EN = 1'b1 */
USB11PHY_SET8(0x1a, 0x10);
udelay(800);
/* force enter device mode */
USB11PHY_CLR8(0x6c, 0x10);
USB11PHY_SET8(0x6c, 0x2E);
USB11PHY_SET8(0x6d, 0x3E);
INFO("[Flow][USB11]%s:%d\n", __func__, __LINE__);
/* force enter host mode */
udelay(100);
USB11PHY_SET8(0x6d, 0x3e);
USB11PHY_READ8(0x6d);
USB11PHY_SET8(0x6c, 0x10);
USB11PHY_CLR8(0x6c, 0x2e);
udelay(5);
USB11PHY_CLR8(0x6c, 0x10);
USB11PHY_SET8(0x6c, 0x2c);
USB11PHY_READ8(0x6c);
INFO("[Flow][USB11]%s:%d\n", __func__, __LINE__);
}
static bool clock_enabled;
void mt65xx_usb11_clock_enable(bool enable)
{
INFO("[Flow][USB]mt65xx_usb11_clock_enable++\r\n");
if (enable) {
if (clock_enabled) { /*already enable*/
/* do nothing */
INFO("[Flow][USB]already enable\r\n");
} else {
#if 0
enable_phy_clock(enable);
INFO("[Flow][USB]enable usb11 clock ++\r\n");
enable_mcu_clock(true);
/* clk_enable(usb_clk); */
clk_enable(icusb_clk);
#endif
clock_enabled = true;
}
} else {
if (!clock_enabled) {/*already disabled.*/
/* do nothing */
INFO("[Flow][USB]already disabled\r\n");
} else {
INFO("[Flow][USB]disable usb11 clock --\r\n");
#if 0
clk_disable(icusb_clk);
/* clk_disable(usb_clk); */
enable_mcu_clock(false);
enable_phy_clock(enable);
#endif
clock_enabled = false;
}
}
}
void mt_usb11_poweron(struct musbfsh *musbfsh, int on)
{
static bool recover;
INFO("mt65xx_usb11_poweron++\r\n");
INFO("[Flow][USB11]%s:%d\n", __func__, __LINE__);
if (on) {
if (musbfsh_power) {
/* do nothing */
INFO("[Flow][USB]power on\r\n");
} else {
mt65xx_usb11_clock_enable(true);
INFO("[Flow][USB11]%s:%d\n", __func__, __LINE__);
if (!recover) {
INFO("[Flow][USB11]%s:%d\n", __func__, __LINE__);
/*mt65xx_usb11_phy_poweron();*/
mt65xx_usb11_phy_recover();
/*mt81xx_usb11_phy_recover();*/
recover = true;
} else {
INFO("[Flow][USB11]%s:%d\n", __func__, __LINE__);
mt65xx_usb11_phy_recover();
/*mt81xx_usb11_phy_recover();*/
}
musbfsh_power = true;
}
} else {
if (!musbfsh_power) {
/* do nothing */
INFO("[Flow][USB]power off\r\n");
} else {
mt65xx_usb11_phy_savecurrent();
mt65xx_usb11_clock_enable(false);
musbfsh_power = false;
}
}
}
void mt_usb11_set_vbus(struct musbfsh *musbfsh, int is_on)
{
int ret;
INFO("is_on=%d\n", is_on);
#ifdef CONFIG_EDGETPU_PMIC_SUPPORT
if (musbfsh->vbus_supply) {
if (is_on) {
ret = regulator_enable(musbfsh->vbus_supply);
if (ret) {
ERR("Failed to enable VBUS\n");
} else {
WARNING("Enable VBUS\n");
}
} else {
ret = regulator_disable(musbfsh->vbus_supply);
if (ret) {
ERR("Failed to disable VBUS\n");
} else {
WARNING("Disable VBUS\n");
}
}
}
#endif
}
void musbfs_check_mpu_violation(u32 addr, int wr_vio)
{
void __iomem *mregs = (void *)USB_BASE;
INFO(KERN_CRIT "MUSB checks EMI MPU violation.\n");
INFO(KERN_CRIT "addr = 0x%x, %s violation.\n", addr, wr_vio ? "Write" : "Read");
INFO(KERN_CRIT "POWER = 0x%x,DEVCTL= 0x%x.\n", musbfsh_readb(mregs, MUSBFSH_POWER),
musbfsh_readb((void __iomem *)USB11_BASE, MUSBFSH_DEVCTL));
INFO(KERN_CRIT "DMA_CNTLch0 0x%04x,DMA_ADDRch0 0x%08x,DMA_COUNTch0 0x%08x\n",
musbfsh_readw(mregs, 0x204), musbfsh_readl(mregs, 0x208), musbfsh_readl(mregs,
0x20C));
INFO(KERN_CRIT "DMA_CNTLch1 0x%04x,DMA_ADDRch1 0x%08x,DMA_COUNTch1 0x%08x\n",
musbfsh_readw(mregs, 0x214), musbfsh_readl(mregs, 0x218), musbfsh_readl(mregs,
0x21C));
INFO(KERN_CRIT "DMA_CNTLch2 0x%04x,DMA_ADDRch2 0x%08x,DMA_COUNTch2 0x%08x\n",
musbfsh_readw(mregs, 0x224), musbfsh_readl(mregs, 0x228), musbfsh_readl(mregs,
0x22C));
INFO(KERN_CRIT "DMA_CNTLch3 0x%04x,DMA_ADDRch3 0x%08x,DMA_COUNTch3 0x%08x\n",
musbfsh_readw(mregs, 0x234), musbfsh_readl(mregs, 0x238), musbfsh_readl(mregs,
0x23C));
INFO(KERN_CRIT "DMA_CNTLch4 0x%04x,DMA_ADDRch4 0x%08x,DMA_COUNTch4 0x%08x\n",
musbfsh_readw(mregs, 0x244), musbfsh_readl(mregs, 0x248), musbfsh_readl(mregs,
0x24C));
INFO(KERN_CRIT "DMA_CNTLch5 0x%04x,DMA_ADDRch5 0x%08x,DMA_COUNTch5 0x%08x\n",
musbfsh_readw(mregs, 0x254), musbfsh_readl(mregs, 0x258), musbfsh_readl(mregs,
0x25C));
INFO(KERN_CRIT "DMA_CNTLch6 0x%04x,DMA_ADDRch6 0x%08x,DMA_COUNTch6 0x%08x\n",
musbfsh_readw(mregs, 0x264), musbfsh_readl(mregs, 0x268), musbfsh_readl(mregs,
0x26C));
INFO(KERN_CRIT "DMA_CNTLch7 0x%04x,DMA_ADDRch7 0x%08x,DMA_COUNTch7 0x%08x\n",
musbfsh_readw(mregs, 0x274), musbfsh_readl(mregs, 0x278), musbfsh_readl(mregs,
0x27C));
}
int mt_usb11_init(struct musbfsh *musbfsh)
{
INFO("++\n");
if (!musbfsh) {
ERR("musbfsh_platform_init,error,musbfsh is NULL");
return -1;
}
INFO("[Flow][USB11]%s:%d\n", __func__, __LINE__);
mt_usb11_poweron(musbfsh, true);
return 0;
}
int mt_usb11_exit(struct musbfsh *musbfsh)
{
INFO("++\n");
mt_usb11_poweron(musbfsh, false);
/* put it here because we can't shutdown PHY power during suspend */
/* hwPowerDown(MT65XX_POWER_LDO_VUSB, "USB11"); */
return 0;
}
void musbfsh_hcd_release(struct device *dev)
{
/* INFO("musbfsh_hcd_release++,dev = 0x%08X.\n", (uint32_t)dev);*/
}
static const struct musbfsh_platform_ops mt_usb11_ops = {
.init = mt_usb11_init,
.exit = mt_usb11_exit,
.set_vbus = mt_usb11_set_vbus,
.set_power = mt_usb11_poweron,
};
static u64 mt_usb11_dmamask = DMA_BIT_MASK(32);
static int __init mt_usb11_probe(struct platform_device *pdev)
{
struct musbfsh_hdrc_platform_data *pdata = pdev->dev.platform_data;
struct platform_device *musbfsh;
struct mt_usb11_glue *glue;
struct musbfsh_hdrc_config *config;
int ret = -ENOMEM;
int musbfshid;
struct device_node *np = pdev->dev.of_node;
INFO("[Flow][USB11]%s:%d\n", __func__, __LINE__);
ret = mt_usb11_clock_prepare();
if (ret) {
dev_err(&pdev->dev, "musbfsh clock prepare fail\n");
goto err0;
}
glue = kzalloc(sizeof(*glue), GFP_KERNEL);
if (!glue) {
dev_err(&pdev->dev, "failed to allocate glue context\n");
goto err0;
}
/* get the musbfsh id */
musbfshid = musbfsh_get_id(&pdev->dev, GFP_KERNEL);
if (musbfshid < 0) {
dev_err(&pdev->dev, "failed to allocate musbfsh id\n");
ret = -ENOMEM;
goto err1;
}
musbfsh = platform_device_alloc("musbfsh-hdrc", musbfshid);
if (!musbfsh) {
dev_err(&pdev->dev, "failed to allocate musb device\n");
goto err2;
}
#ifdef CONFIG_OF
usb11_dts_np = pdev->dev.of_node;
INFO("[usb11] usb11_dts_np %p\n", usb11_dts_np);
/*usb_irq_number1 = irq_of_parse_and_map(pdev->dev.of_node, 0);
* usb_mac = (unsigned long)of_iomap(pdev->dev.of_node, 0);
* usb_phy_base = (unsigned long)of_iomap(pdev->dev.of_node, 1);
*/
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
ERR("failed to allocate musb platform data\n");
goto err2;
}
config = devm_kzalloc(&pdev->dev, sizeof(*config), GFP_KERNEL);
if (!config) {
ERR("failed to allocate musb hdrc config\n");
goto err2;
}
of_property_read_u32(np, "mode", (u32 *)&pdata->mode);
/*of_property_read_u32(np, "dma_channels", (u32 *)&config->dma_channels); */
of_property_read_u32(np, "num_eps", (u32 *)&config->num_eps);
config->multipoint = of_property_read_bool(np, "multipoint");
/*
* config->dyn_fifo = of_property_read_bool(np, "dyn_fifo");
* config->soft_con = of_property_read_bool(np, "soft_con");
* config->dma = of_property_read_bool(np, "dma");
*/
pdata->config = config;
INFO("[Flow][USB11]mode = %d ,num_eps = %d,multipoint = %d\n", pdata->mode,
config->num_eps, config->multipoint);
#endif
musbfsh->id = musbfshid;
musbfsh->dev.parent = &pdev->dev;
musbfsh->dev.dma_mask = &mt_usb11_dmamask;
musbfsh->dev.coherent_dma_mask = mt_usb11_dmamask;
#ifdef CONFIG_OF
pdev->dev.dma_mask = &mt_usb11_dmamask;
pdev->dev.coherent_dma_mask = mt_usb11_dmamask;
arch_setup_dma_ops(&musbfsh->dev, 0, mt_usb11_dmamask, NULL, 0);
#endif
glue->dev = &pdev->dev;
glue->musbfsh = musbfsh;
pdata->platform_ops = &mt_usb11_ops;
platform_set_drvdata(pdev, glue);
ret = platform_device_add_resources(musbfsh, pdev->resource, pdev->num_resources);
if (ret) {
dev_err(&pdev->dev, "failed to add resources\n");
goto err3;
}
ret = platform_device_add_data(musbfsh, pdata, sizeof(*pdata));
if (ret) {
dev_err(&pdev->dev, "failed to add platform_data\n");
goto err3;
}
ret = platform_device_add(musbfsh);
if (ret) {
dev_err(&pdev->dev, "failed to register musbfsh device\n");
goto err3;
}
return 0;
err3:
platform_device_put(musbfsh);
err2:
musbfsh_put_id(&pdev->dev, musbfshid);
err1:
kfree(glue);
err0:
return ret;
}
static int __exit mt_usb_remove(struct platform_device *pdev)
{
struct mt_usb11_glue *glue = platform_get_drvdata(pdev);
musbfsh_put_id(&pdev->dev, glue->musbfsh->id);
platform_device_del(glue->musbfsh);
platform_device_put(glue->musbfsh);
kfree(glue);
return 0;
}
static struct platform_driver mt_usb11_driver = {
.remove = __exit_p(mt_usb_remove),
.probe = mt_usb11_probe,
.driver = {
.name = "mt_usb11",
#ifdef CONFIG_OF
.of_match_table = apusb_of_ids,
#endif
},
};
int usb11_init(void)
{
INFO("[Flow][USB11]%s:%d\n", __func__, __LINE__);
return platform_driver_register(&mt_usb11_driver);
}
/*mubsys_initcall(usb11_init);*/
void usb11_exit(void)
{
platform_driver_unregister(&mt_usb11_driver);
}
/*module_exit(usb11_exit) */