blob: 12b6774cb64f54814eb7482aebcc9dd48f3eba39 [file] [log] [blame]
/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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.
*/
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/wakelock.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/usb.h>
#include <linux/usb/gadget.h>
#include <linux/usb/msm_hsusb_hw.h>
#include <linux/usb/msm_hsusb.h>
#include <mach/clk.h>
#include <mach/msm_iomap.h>
#include <mach/msm_xo.h>
#include "ci13xxx_udc.c"
#define MSM_USB_BASE (mhsic->regs)
#define ULPI_IO_TIMEOUT_USEC (10 * 1000)
#define USB_PHY_VDD_DIG_VOL_SUSP_MIN 500000 /* uV */
#define USB_PHY_VDD_DIG_VOL_MIN 1045000 /* uV */
#define USB_PHY_VDD_DIG_VOL_MAX 1320000 /* uV */
#define USB_PHY_VDD_DIG_LOAD 49360 /* uA */
#define LINK_RESET_TIMEOUT_USEC (250 * 1000)
#define PHY_SUSPEND_TIMEOUT_USEC (500 * 1000)
#define PHY_RESUME_TIMEOUT_USEC (100 * 1000)
#define HSIC_CFG_REG 0x30
#define HSIC_IO_CAL_PER_REG 0x33
#define HSIC_DBG1_REG 0x38
struct msm_hsic_per *the_mhsic;
struct msm_hsic_per {
struct device *dev;
struct clk *iface_clk;
struct clk *core_clk;
struct clk *alt_core_clk;
struct clk *phy_clk;
struct clk *cal_clk;
struct regulator *hsic_vddcx;
bool async_int;
void __iomem *regs;
int irq;
atomic_t in_lpm;
struct wake_lock wlock;
struct msm_xo_voter *xo_handle;
struct workqueue_struct *wq;
struct work_struct suspend_w;
};
static int msm_hsic_init_vddcx(struct msm_hsic_per *mhsic, int init)
{
int ret = 0;
if (!init)
goto disable_reg;
mhsic->hsic_vddcx = regulator_get(mhsic->dev, "HSIC_VDDCX");
if (IS_ERR(mhsic->hsic_vddcx)) {
dev_err(mhsic->dev, "unable to get hsic vddcx\n");
return PTR_ERR(mhsic->hsic_vddcx);
}
ret = regulator_set_voltage(mhsic->hsic_vddcx,
USB_PHY_VDD_DIG_VOL_MIN,
USB_PHY_VDD_DIG_VOL_MAX);
if (ret) {
dev_err(mhsic->dev, "unable to set the voltage"
"for hsic vddcx\n");
goto reg_set_voltage_err;
}
ret = regulator_set_optimum_mode(mhsic->hsic_vddcx,
USB_PHY_VDD_DIG_LOAD);
if (ret < 0) {
pr_err("%s: Unable to set optimum mode of the regulator:"
"VDDCX\n", __func__);
goto reg_optimum_mode_err;
}
ret = regulator_enable(mhsic->hsic_vddcx);
if (ret) {
dev_err(mhsic->dev, "unable to enable hsic vddcx\n");
goto reg_enable_err;
}
return 0;
disable_reg:
regulator_disable(mhsic->hsic_vddcx);
reg_enable_err:
regulator_set_optimum_mode(mhsic->hsic_vddcx, 0);
reg_optimum_mode_err:
regulator_set_voltage(mhsic->hsic_vddcx, 0,
USB_PHY_VDD_DIG_VOL_MIN);
reg_set_voltage_err:
regulator_put(mhsic->hsic_vddcx);
return ret;
}
static int ulpi_write(struct msm_hsic_per *mhsic, u32 val, u32 reg)
{
int cnt = 0;
/* initiate write operation */
writel_relaxed(ULPI_RUN | ULPI_WRITE |
ULPI_ADDR(reg) | ULPI_DATA(val),
USB_ULPI_VIEWPORT);
/* wait for completion */
while (cnt < ULPI_IO_TIMEOUT_USEC) {
if (!(readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_RUN))
break;
udelay(1);
cnt++;
}
if (cnt >= ULPI_IO_TIMEOUT_USEC) {
dev_err(mhsic->dev, "ulpi_write: timeout\n");
return -ETIMEDOUT;
}
return 0;
}
static int msm_hsic_phy_clk_reset(struct msm_hsic_per *mhsic)
{
int ret;
ret = clk_reset(mhsic->core_clk, CLK_RESET_ASSERT);
if (ret) {
clk_disable(mhsic->alt_core_clk);
dev_err(mhsic->dev, "usb phy clk assert failed\n");
return ret;
}
usleep_range(10000, 12000);
clk_disable(mhsic->alt_core_clk);
ret = clk_reset(mhsic->core_clk, CLK_RESET_DEASSERT);
if (ret)
dev_err(mhsic->dev, "usb phy clk deassert failed\n");
return ret;
}
static int msm_hsic_phy_reset(struct msm_hsic_per *mhsic)
{
u32 val;
int ret;
ret = msm_hsic_phy_clk_reset(mhsic);
if (ret)
return ret;
val = readl_relaxed(USB_PORTSC) & ~PORTSC_PTS_MASK;
writel_relaxed(val | PORTSC_PTS_ULPI, USB_PORTSC);
/*
* Ensure that RESET operation is completed before
* turning off clock.
*/
mb();
dev_dbg(mhsic->dev, "phy_reset: success\n");
return 0;
}
static int msm_hsic_enable_clocks(struct platform_device *pdev,
struct msm_hsic_per *mhsic, bool enable)
{
int ret = 0;
if (!enable)
goto put_clocks;
mhsic->iface_clk = clk_get(&pdev->dev, "iface_clk");
if (IS_ERR(mhsic->iface_clk)) {
dev_err(mhsic->dev, "failed to get iface_clk\n");
ret = PTR_ERR(mhsic->iface_clk);
goto put_iface_clk;
}
mhsic->core_clk = clk_get(&pdev->dev, "core_clk");
if (IS_ERR(mhsic->core_clk)) {
dev_err(mhsic->dev, "failed to get core_clk\n");
ret = PTR_ERR(mhsic->core_clk);
goto put_core_clk;
}
mhsic->phy_clk = clk_get(&pdev->dev, "phy_clk");
if (IS_ERR(mhsic->phy_clk)) {
dev_err(mhsic->dev, "failed to get phy_clk\n");
ret = PTR_ERR(mhsic->phy_clk);
goto put_phy_clk;
}
mhsic->alt_core_clk = clk_get(&pdev->dev, "alt_core_clk");
if (IS_ERR(mhsic->alt_core_clk)) {
dev_err(mhsic->dev, "failed to get alt_core_clk\n");
ret = PTR_ERR(mhsic->alt_core_clk);
goto put_alt_core_clk;
}
mhsic->cal_clk = clk_get(&pdev->dev, "cal_clk");
if (IS_ERR(mhsic->cal_clk)) {
dev_err(mhsic->dev, "failed to get cal_clk\n");
ret = PTR_ERR(mhsic->cal_clk);
goto put_cal_clk;
}
clk_enable(mhsic->iface_clk);
clk_enable(mhsic->core_clk);
clk_enable(mhsic->phy_clk);
clk_enable(mhsic->alt_core_clk);
clk_enable(mhsic->cal_clk);
return 0;
put_clocks:
clk_disable(mhsic->iface_clk);
clk_disable(mhsic->core_clk);
clk_disable(mhsic->phy_clk);
clk_disable(mhsic->alt_core_clk);
clk_disable(mhsic->cal_clk);
put_cal_clk:
clk_put(mhsic->cal_clk);
put_alt_core_clk:
clk_put(mhsic->alt_core_clk);
put_phy_clk:
clk_put(mhsic->phy_clk);
put_core_clk:
clk_put(mhsic->core_clk);
put_iface_clk:
clk_put(mhsic->iface_clk);
return ret;
}
static int msm_hsic_reset(struct msm_hsic_per *mhsic)
{
int cnt = 0;
int ret;
ret = msm_hsic_phy_reset(mhsic);
if (ret) {
dev_err(mhsic->dev, "phy_reset failed\n");
return ret;
}
writel_relaxed(USBCMD_RESET, USB_USBCMD);
while (cnt < LINK_RESET_TIMEOUT_USEC) {
if (!(readl_relaxed(USB_USBCMD) & USBCMD_RESET))
break;
udelay(1);
cnt++;
}
if (cnt >= LINK_RESET_TIMEOUT_USEC)
return -ETIMEDOUT;
/* Reset PORTSC and select ULPI phy */
writel_relaxed(0x80000000, USB_PORTSC);
return 0;
}
static void msm_hsic_start(void)
{
int ret;
/* programmable length of connect signaling (33.2ns) */
ret = ulpi_write(the_mhsic, 3, HSIC_DBG1_REG);
if (ret) {
pr_err("%s: Unable to program length of connect signaling\n",
__func__);
}
/*set periodic calibration interval to ~2.048sec in HSIC_IO_CAL_REG */
ret = ulpi_write(the_mhsic, 0xFF, HSIC_IO_CAL_PER_REG);
if (ret) {
pr_err("%s: Unable to set periodic calibration interval\n",
__func__);
}
/* Enable periodic IO calibration in HSIC_CFG register */
ret = ulpi_write(the_mhsic, 0xE9, HSIC_CFG_REG);
if (ret) {
pr_err("%s: Unable to enable periodic IO calibration\n",
__func__);
}
}
#ifdef CONFIG_PM_SLEEP
static int msm_hsic_suspend(struct msm_hsic_per *mhsic)
{
int cnt = 0, ret;
u32 val;
if (atomic_read(&mhsic->in_lpm)) {
dev_dbg(mhsic->dev, "%s called while in lpm\n", __func__);
return 0;
}
disable_irq(mhsic->irq);
/*
* PHY may take some time or even fail to enter into low power
* mode (LPM). Hence poll for 500 msec and reset the PHY and link
* in failure case.
*/
val = readl_relaxed(USB_PORTSC) | PORTSC_PHCD;
writel_relaxed(val, USB_PORTSC);
while (cnt < PHY_SUSPEND_TIMEOUT_USEC) {
if (readl_relaxed(USB_PORTSC) & PORTSC_PHCD)
break;
udelay(1);
cnt++;
}
if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) {
dev_err(mhsic->dev, "Unable to suspend PHY\n");
msm_hsic_reset(mhsic);
}
/*
* PHY has capability to generate interrupt asynchronously in low
* power mode (LPM). This interrupt is level triggered. So USB IRQ
* line must be disabled till async interrupt enable bit is cleared
* in USBCMD register. Assert STP (ULPI interface STOP signal) to
* block data communication from PHY.
*/
writel_relaxed(readl_relaxed(USB_USBCMD) | ASYNC_INTR_CTRL |
ULPI_STP_CTRL, USB_USBCMD);
/*
* Ensure that hardware is put in low power mode before
* clocks are turned OFF and VDD is allowed to minimize.
*/
mb();
clk_disable(mhsic->iface_clk);
clk_disable(mhsic->core_clk);
clk_disable(mhsic->phy_clk);
clk_disable(mhsic->cal_clk);
ret = msm_xo_mode_vote(mhsic->xo_handle, MSM_XO_MODE_OFF);
if (ret)
dev_err(mhsic->dev, "%s failed to devote for TCXO %d\n"
, __func__, ret);
ret = regulator_set_voltage(mhsic->hsic_vddcx,
USB_PHY_VDD_DIG_VOL_SUSP_MIN,
USB_PHY_VDD_DIG_VOL_MAX);
if (ret < 0)
dev_err(mhsic->dev, "unable to set vddcx voltage: min:0.5v max:1.32v\n");
if (device_may_wakeup(mhsic->dev))
enable_irq_wake(mhsic->irq);
atomic_set(&mhsic->in_lpm, 1);
enable_irq(mhsic->irq);
wake_unlock(&mhsic->wlock);
dev_info(mhsic->dev, "HSIC-USB in low power mode\n");
return 0;
}
static int msm_hsic_resume(struct msm_hsic_per *mhsic)
{
int cnt = 0, ret;
unsigned temp;
if (!atomic_read(&mhsic->in_lpm)) {
dev_dbg(mhsic->dev, "%s called while not in lpm\n", __func__);
return 0;
}
wake_lock(&mhsic->wlock);
ret = regulator_set_voltage(mhsic->hsic_vddcx,
USB_PHY_VDD_DIG_VOL_MIN,
USB_PHY_VDD_DIG_VOL_MAX);
if (ret < 0)
dev_err(mhsic->dev,
"unable to set vddcx voltage: min:1.045v max:1.32v\n");
ret = msm_xo_mode_vote(mhsic->xo_handle, MSM_XO_MODE_ON);
if (ret)
dev_err(mhsic->dev, "%s failed to vote for TCXO %d\n",
__func__, ret);
clk_enable(mhsic->iface_clk);
clk_enable(mhsic->core_clk);
clk_enable(mhsic->phy_clk);
clk_enable(mhsic->cal_clk);
temp = readl_relaxed(USB_USBCMD);
temp &= ~ASYNC_INTR_CTRL;
temp &= ~ULPI_STP_CTRL;
writel_relaxed(temp, USB_USBCMD);
if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD))
goto skip_phy_resume;
temp = readl_relaxed(USB_PORTSC) & ~PORTSC_PHCD;
writel_relaxed(temp, USB_PORTSC);
while (cnt < PHY_RESUME_TIMEOUT_USEC) {
if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD) &&
(readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_SYNC_STATE))
break;
udelay(1);
cnt++;
}
if (cnt >= PHY_RESUME_TIMEOUT_USEC) {
/*
* This is a fatal error. Reset the link and
* PHY to make hsic working.
*/
dev_err(mhsic->dev, "Unable to resume USB. Reset the hsic\n");
msm_hsic_reset(mhsic);
}
skip_phy_resume:
if (device_may_wakeup(mhsic->dev))
disable_irq_wake(mhsic->irq);
atomic_set(&mhsic->in_lpm, 0);
if (mhsic->async_int) {
mhsic->async_int = false;
enable_irq(mhsic->irq);
}
dev_info(mhsic->dev, "HSIC-USB exited from low power mode\n");
return 0;
}
static int msm_hsic_pm_suspend(struct device *dev)
{
struct msm_hsic_per *mhsic = dev_get_drvdata(dev);
dev_dbg(dev, "MSM HSIC Peripheral PM suspend\n");
return msm_hsic_suspend(mhsic);
}
#ifdef CONFIG_PM_RUNTIME
static int msm_hsic_pm_resume(struct device *dev)
{
dev_dbg(dev, "MSM HSIC Peripheral PM resume\n");
/*
* Do not resume hardware as part of system resume,
* rather, wait for the ASYNC INT from the h/w
*/
return 0;
}
#else
static int msm_hsic_pm_resume(struct device *dev)
{
struct msm_hsic_per *mhsic = dev_get_drvdata(dev);
dev_dbg(dev, "MSM HSIC Peripheral PM resume\n");
return msm_hsic_resume(mhsic);
}
#endif
static void msm_hsic_pm_suspend_work(struct work_struct *w)
{
pm_runtime_put_noidle(the_mhsic->dev);
pm_runtime_suspend(the_mhsic->dev);
}
#endif /* CONFIG_PM_SLEEP */
#ifdef CONFIG_PM_RUNTIME
static int msm_hsic_runtime_idle(struct device *dev)
{
dev_dbg(dev, "MSM HSIC Peripheral runtime idle\n");
return 0;
}
static int msm_hsic_runtime_suspend(struct device *dev)
{
struct msm_hsic_per *mhsic = dev_get_drvdata(dev);
dev_dbg(dev, "MSM HSIC Peripheral runtime suspend\n");
return msm_hsic_suspend(mhsic);
}
static int msm_hsic_runtime_resume(struct device *dev)
{
struct msm_hsic_per *mhsic = dev_get_drvdata(dev);
dev_dbg(dev, "MSM HSIC Peripheral runtime resume\n");
pm_runtime_get_noresume(mhsic->dev);
return msm_hsic_resume(mhsic);
}
#endif
#ifdef CONFIG_PM
static const struct dev_pm_ops msm_hsic_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(msm_hsic_pm_suspend, msm_hsic_pm_resume)
SET_RUNTIME_PM_OPS(msm_hsic_runtime_suspend, msm_hsic_runtime_resume,
msm_hsic_runtime_idle)
};
#endif
/**
* Dummy match function - will be called only for HSIC msm
* device (msm_device_gadget_hsic_peripheral).
*/
static inline int __match(struct device *dev, void *data) { return 1; }
static void msm_hsic_connect_peripheral(struct device *msm_udc_dev)
{
struct device *dev;
struct usb_gadget *gadget;
dev = device_find_child(msm_udc_dev, NULL, __match);
gadget = dev_to_usb_gadget(dev);
usb_gadget_vbus_connect(gadget);
}
static irqreturn_t msm_udc_hsic_irq(int irq, void *data)
{
struct msm_hsic_per *mhsic = data;
if (atomic_read(&mhsic->in_lpm)) {
disable_irq_nosync(mhsic->irq);
mhsic->async_int = true;
pm_request_resume(mhsic->dev);
return IRQ_HANDLED;
}
return udc_irq();
}
static void ci13xxx_msm_hsic_notify_event(struct ci13xxx *udc, unsigned event)
{
struct device *dev = udc->gadget.dev.parent;
struct msm_hsic_per *mhsic = the_mhsic;
switch (event) {
case CI13XXX_CONTROLLER_RESET_EVENT:
dev_dbg(dev, "CI13XXX_CONTROLLER_RESET_EVENT received\n");
writel_relaxed(0, USB_AHBBURST);
writel_relaxed(0, USB_AHBMODE);
break;
case CI13XXX_CONTROLLER_CONNECT_EVENT:
dev_dbg(dev, "CI13XXX_CONTROLLER_CONNECT_EVENT received\n");
msm_hsic_start();
break;
case CI13XXX_CONTROLLER_SUSPEND_EVENT:
dev_dbg(dev, "CI13XXX_CONTROLLER_SUSPEND_EVENT received\n");
queue_work(mhsic->wq, &mhsic->suspend_w);
break;
default:
dev_dbg(dev, "unknown ci13xxx_udc event\n");
break;
}
}
static struct ci13xxx_udc_driver ci13xxx_msm_udc_hsic_driver = {
.name = "ci13xxx_msm_hsic",
.flags = CI13XXX_REGS_SHARED |
CI13XXX_PULLUP_ON_VBUS |
CI13XXX_DISABLE_STREAMING |
CI13XXX_ZERO_ITC,
.notify_event = ci13xxx_msm_hsic_notify_event,
};
static int __devinit msm_hsic_probe(struct platform_device *pdev)
{
struct resource *res;
struct msm_hsic_per *mhsic;
int ret = 0;
dev_dbg(&pdev->dev, "msm-hsic probe\n");
mhsic = kzalloc(sizeof(struct msm_hsic_per), GFP_KERNEL);
if (!mhsic) {
dev_err(&pdev->dev, "unable to allocate msm_hsic\n");
return -ENOMEM;
}
the_mhsic = mhsic;
platform_set_drvdata(pdev, mhsic);
mhsic->dev = &pdev->dev;
mhsic->irq = platform_get_irq(pdev, 0);
if (mhsic->irq < 0) {
dev_err(&pdev->dev, "Unable to get IRQ resource\n");
ret = mhsic->irq;
goto error;
}
mhsic->wq = alloc_workqueue("mhsic_wq", WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
if (!mhsic->wq) {
pr_err("%s: Unable to create workqueue mhsic wq\n",
__func__);
ret = -ENOMEM;
goto error;
}
INIT_WORK(&mhsic->suspend_w, msm_hsic_pm_suspend_work);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Unable to get memory resource\n");
ret = -ENODEV;
goto error;
}
mhsic->regs = ioremap(res->start, resource_size(res));
if (!mhsic->regs) {
dev_err(&pdev->dev, "ioremap failed\n");
ret = -ENOMEM;
goto unmap;
}
dev_info(&pdev->dev, "HSIC Peripheral regs = %p\n", mhsic->regs);
mhsic->xo_handle = msm_xo_get(MSM_XO_CXO, "hsic_peripheral");
if (IS_ERR(mhsic->xo_handle)) {
dev_err(&pdev->dev, "%s not able to get the handle "
"to vote for TCXO\n", __func__);
ret = PTR_ERR(mhsic->xo_handle);
goto unmap;
}
ret = msm_xo_mode_vote(mhsic->xo_handle, MSM_XO_MODE_ON);
if (ret) {
dev_err(&pdev->dev, "%s failed to vote for TCXO %d\n",
__func__, ret);
goto free_xo_handle;
}
ret = msm_hsic_enable_clocks(pdev, mhsic, true);
if (ret) {
dev_err(&pdev->dev, "msm_hsic_enable_clocks failed\n");
ret = -ENODEV;
goto deinit_clocks;
}
ret = msm_hsic_init_vddcx(mhsic, 1);
if (ret) {
dev_err(&pdev->dev, "unable to initialize VDDCX\n");
ret = -ENODEV;
goto deinit_vddcx;
}
ret = msm_hsic_reset(mhsic);
if (ret) {
dev_err(&pdev->dev, "msm_hsic_reset failed\n");
ret = -ENODEV;
goto deinit_vddcx;
}
ret = udc_probe(&ci13xxx_msm_udc_hsic_driver, &pdev->dev, mhsic->regs);
if (ret < 0) {
dev_err(&pdev->dev, "udc_probe failed\n");
ret = -ENODEV;
goto deinit_vddcx;
}
msm_hsic_connect_peripheral(&pdev->dev);
device_init_wakeup(&pdev->dev, 1);
wake_lock_init(&mhsic->wlock, WAKE_LOCK_SUSPEND, dev_name(&pdev->dev));
wake_lock(&mhsic->wlock);
ret = request_irq(mhsic->irq, msm_udc_hsic_irq,
IRQF_SHARED, pdev->name, mhsic);
if (ret < 0) {
dev_err(&pdev->dev, "request_irq failed\n");
ret = -ENODEV;
goto udc_remove;
}
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
return 0;
udc_remove:
udc_remove();
deinit_vddcx:
msm_hsic_init_vddcx(mhsic, 0);
deinit_clocks:
msm_hsic_enable_clocks(pdev, mhsic, 0);
msm_xo_mode_vote(mhsic->xo_handle, MSM_XO_MODE_OFF);
free_xo_handle:
msm_xo_put(mhsic->xo_handle);
unmap:
iounmap(mhsic->regs);
error:
destroy_workqueue(mhsic->wq);
kfree(mhsic);
return ret;
}
static int __devexit hsic_msm_remove(struct platform_device *pdev)
{
struct msm_hsic_per *mhsic = platform_get_drvdata(pdev);
device_init_wakeup(&pdev->dev, 0);
pm_runtime_disable(&pdev->dev);
pm_runtime_set_suspended(&pdev->dev);
msm_hsic_init_vddcx(mhsic, 0);
msm_hsic_enable_clocks(pdev, mhsic, 0);
msm_xo_put(mhsic->xo_handle);
wake_lock_destroy(&mhsic->wlock);
destroy_workqueue(mhsic->wq);
udc_remove();
iounmap(mhsic->regs);
kfree(mhsic);
return 0;
}
static struct platform_driver msm_hsic_peripheral_driver = {
.probe = msm_hsic_probe,
.remove = __devexit_p(hsic_msm_remove),
.driver = {
.name = "msm_hsic_peripheral",
#ifdef CONFIG_PM
.pm = &msm_hsic_dev_pm_ops,
#endif
},
};
static int __init msm_hsic_peripheral_init(void)
{
return platform_driver_probe(&msm_hsic_peripheral_driver,
msm_hsic_probe);
}
static void __exit msm_hsic_peripheral_exit(void)
{
platform_driver_unregister(&msm_hsic_peripheral_driver);
}
module_init(msm_hsic_peripheral_init);
module_exit(msm_hsic_peripheral_exit);