aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/host/shost/shost_driver.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/shost/shost_driver.c')
-rw-r--r--drivers/usb/host/shost/shost_driver.c450
1 files changed, 450 insertions, 0 deletions
diff --git a/drivers/usb/host/shost/shost_driver.c b/drivers/usb/host/shost/shost_driver.c
new file mode 100644
index 0000000..f10136c
--- /dev/null
+++ b/drivers/usb/host/shost/shost_driver.c
@@ -0,0 +1,450 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * @file s3c-otg-hcdi-driver.c
+ * @brief It provides functions related with module for OTGHCD driver.
+ * @version
+ * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com)
+ * : Creating the initial version of this code
+ * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com)
+ * : Optimizing for performance
+ * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com)
+ * : Modifying for successful rmmod & disconnecting
+ * @see None
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h> /* for SA_SHIRQ */
+#include <mach/map.h> /* address for smdk */
+#include <linux/dma-mapping.h> /* dma_alloc_coherent */
+#include <linux/ioport.h> /* request_mem_request ... */
+#include <asm/irq.h> /* for IRQ_OTG */
+#include <linux/clk.h>
+
+
+#include "shost.h"
+
+
+static inline struct sec_otghost *hcd_to_sec_otghost(struct usb_hcd *hcd)
+{
+ return (struct sec_otghost *)(hcd->hcd_priv);
+}
+static inline struct usb_hcd *sec_otghost_to_hcd(struct sec_otghost *otghost)
+{
+ return container_of((void *) otghost, struct usb_hcd, hcd_priv);
+}
+
+
+#include "shost_oci.c"
+#include "shost_transfer.c"
+#include "shost_roothub.c"
+#include "shost_hcd.c"
+
+volatile u8 *g_pUDCBase;
+struct usb_hcd *g_pUsbHcd;
+
+static const char gHcdName[] = "EMSP_OTG_HCD";
+static struct platform_device *g_pdev;
+
+
+static void otg_power_work(struct work_struct *work)
+{
+ struct sec_otghost *otghost = container_of(work,
+ struct sec_otghost, work);
+ struct sec_otghost_data *hdata = otghost->otg_data;
+
+ if (hdata && hdata->set_pwr_cb) {
+ pr_info("otg power off - don't turn off the power\n");
+ hdata->set_pwr_cb(0);
+#ifdef CONFIG_USB_HOST_NOTIFY
+ if (hdata->host_notify_cb)
+ hdata->host_notify_cb(NOTIFY_HOST_OVERCURRENT);
+#endif
+ } else {
+ otg_err(true, "invalid otghost data\n");
+ }
+}
+
+static int s5pc110_mapping_resources(struct platform_device *pdev)
+{
+ g_pUsbHcd->rsrc_start = pdev->resource[0].start;
+ g_pUsbHcd->rsrc_len = pdev->resource[0].end -
+ pdev->resource[0].start + 1;
+
+ if (!request_mem_region(g_pUsbHcd->rsrc_start,
+ g_pUsbHcd->rsrc_len, gHcdName)) {
+ otg_err(OTG_DBG_OTGHCDI_DRIVER,
+ "failed to request_mem_region\n");
+ return -EBUSY;
+ }
+
+ pr_info("otg rsrc_start %llu, ren %llu\n",
+ g_pUsbHcd->rsrc_start,
+ g_pUsbHcd->rsrc_len);
+
+ pr_info("otg regs : %p\n", S3C_VA_HSOTG);
+
+ /* Physical address => Virtual address */
+ g_pUsbHcd->regs = S3C_VA_HSOTG;
+ g_pUDCBase = (u8 *)g_pUsbHcd->regs;
+
+ return 0;
+}
+
+static void s5pc110_prevent_suspend(struct usb_device *rhdev)
+{
+ dev_info(&rhdev->dev, "otg host do not enter suspend.\n");
+ pm_runtime_disable(&rhdev->dev);
+}
+
+static int s5pc110_start_otg(u32 regs)
+{
+ int ret_val = 0;
+ u32 reg_val = 0;
+ struct platform_device *pdev = g_pdev;
+ struct sec_otghost *otghost = NULL;
+ struct sec_otghost_data *otg_data = dev_get_platdata(&pdev->dev);
+
+ pr_info("%s + regs=0x%x\n", __func__, regs);
+
+ /* 1. hcd */
+ g_pUsbHcd = usb_create_hcd(&s5pc110_otg_hc_driver, &pdev->dev,
+ "s3cotg");/*pdev->dev.bus_id*/
+ if (g_pUsbHcd == NULL) {
+ otg_err(1, "failed to usb_create_hcd\n");
+ return -ENOMEM;
+ }
+ g_pUsbHcd->self.otg_port = 1;
+
+ /* sec_otghost */
+ otghost = hcd_to_sec_otghost(g_pUsbHcd);
+ if (otghost == NULL) {
+ otg_err(true, "failed to get otghost hcd\n");
+ ret_val = USB_ERR_FAIL;
+ goto err_out_create_hcd;
+ }
+ otghost->otg_data = otg_data;
+
+ /* 2. wake lock */
+ wake_lock_init(&otghost->wake_lock, WAKE_LOCK_SUSPEND, "usb_otg");
+ wake_lock(&otghost->wake_lock);
+
+ /* base address */
+ if (!regs) {
+ pr_info("otg mapping hcd resource\n");
+ ret_val = s5pc110_mapping_resources(pdev);
+ if (ret_val)
+ goto err_out_create_hcd;
+ } else
+ g_pUDCBase = (u8 *)regs;
+
+ pr_info("otg g_pUDCBase 0x%p\n", g_pUDCBase);
+
+ /* 3. workqueue */
+ INIT_WORK(&otghost->work, otg_power_work);
+ otghost->wq = create_singlethread_workqueue("sec_otghostd");
+
+ /* 4. phy */
+ ret_val = otg_hcd_init_modules(otghost);
+ if (ret_val != USB_ERR_SUCCESS) {
+ otg_err(OTG_DBG_OTGHCDI_DRIVER,
+ "failed to otg_hcd_init_modules\n");
+ ret_val = USB_ERR_FAIL;
+ goto err_out_create_hcd;
+ }
+
+ /**
+ * Attempt to ensure this device is really a s5pc110 USB-OTG Controller.
+ * Read and verify the SNPSID register contents. The value should be
+ * 0x45F42XXX, which corresponds to "OT2", as in "OTG version 2.XX".
+ */
+ reg_val = read_reg_32(0x40);
+ pr_info("otg reg 0x40 = %x\n", reg_val);
+ if ((reg_val & 0xFFFFF000) != 0x4F542000) {
+ otg_err(OTG_DBG_OTGHCDI_DRIVER,
+ "Bad value for SNPSID: 0x%x\n", reg_val);
+ ret_val = -EINVAL;
+ goto err_out_create_hcd_init;
+ }
+
+#ifdef CONFIG_USB_SEC_WHITELIST
+ if (otg_data->sec_whlist_table_num)
+ g_pUsbHcd->sec_whlist_table_num =
+ otg_data->sec_whlist_table_num;
+#endif
+
+ /* 5. hcd
+ * Finish generic HCD initialization and start the HCD. This function
+ * allocates the DMA buffer pool, registers the USB bus, requests the
+ * IRQ line, and calls s5pc110_otghcd_start method.
+ */
+ ret_val = usb_add_hcd(g_pUsbHcd,
+ pdev->resource[1].start, IRQF_DISABLED);
+ if (ret_val < 0) {
+ otg_err(OTG_DBG_OTGHCDI_DRIVER,
+ "Failed to add hcd driver\n");
+ goto err_out_create_hcd_init;
+ }
+
+ s5pc110_prevent_suspend(g_pUsbHcd->self.root_hub);
+
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "OTG HCD Initialized HCD, bus=%s, usbbus=%d\n",
+ "C110 OTG Controller", g_pUsbHcd->self.busnum);
+
+ /* otg_print_registers(); */
+ pr_info("%s -\n", __func__);
+
+ return USB_ERR_SUCCESS;
+
+err_out_create_hcd_init:
+ otg_hcd_deinit_modules(otghost);
+ if (!regs)
+ release_mem_region(g_pUsbHcd->rsrc_start, g_pUsbHcd->rsrc_len);
+
+err_out_create_hcd:
+ usb_put_hcd(g_pUsbHcd);
+
+ return ret_val;
+}
+
+static int s5pc110_stop_otg(void)
+{
+ struct sec_otghost *otghost = NULL;
+ struct sec_otghost_data *otgdata = NULL;
+
+ pr_info("%s +\n", __func__);
+
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER, "s5pc110_stop_otg\n");
+
+ otghost = hcd_to_sec_otghost(g_pUsbHcd);
+
+ otg_hcd_deinit_modules(otghost);
+
+ /* 5. hcd */
+ usb_remove_hcd(g_pUsbHcd);
+#if 1
+ if (g_pUDCBase == S3C_VA_HSOTG) {
+ pr_info("otg release_mem_region\n");
+ release_mem_region(g_pUsbHcd->rsrc_start, g_pUsbHcd->rsrc_len);
+ }
+#endif
+ /* 4. phy */
+ otgdata = otghost->otg_data;
+ if (otgdata && otgdata->phy_exit && otgdata->pdev) {
+ pr_info("otg phy_off\n");
+ otgdata->phy_exit(0);
+ clk_disable(otgdata->clk);
+ }
+
+ /* 3. workqueue */
+ destroy_workqueue(otghost->wq);
+
+ /* 2. wake lock */
+ wake_unlock(&otghost->wake_lock);
+ wake_lock_destroy(&otghost->wake_lock);
+
+ /* 1. hcd */
+ usb_put_hcd(g_pUsbHcd);
+
+ pr_info("%s -\n", __func__);
+
+ return 0;
+}
+
+/**
+ * static int s5pc110_otg_drv_probe (struct platform_device *pdev)
+ *
+ * @brief probe function of OTG hcd platform_driver
+ *
+ * @param [in] pdev : pointer of platform_device of otg hcd platform_driver
+ *
+ * @return USB_ERR_SUCCESS : If success
+ * USB_ERR_FAIL : If fail
+ * @remark
+ * it allocates resources of it and call other modules' init function.
+ * then call usb_create_hcd, usb_add_hcd, s5pc110_otghcd_start functions
+ */
+
+static int s5pc110_otg_drv_probe(struct platform_device *pdev)
+{
+ struct sec_otghost_data *otg_data = dev_get_platdata(&pdev->dev);
+ g_pdev = pdev;
+
+ otg_data->clk = clk_get(&pdev->dev, "usbotg");
+
+ if (IS_ERR(otg_data->clk)) {
+ otg_err(OTG_DBG_OTGHCDI_DRIVER,
+ "Failed to get clock\n");
+ return PTR_RET(otg_data->clk);
+ }
+
+ pr_info("otg host_probe start %p\n", s5pc110_start_otg);
+ otg_data->start = s5pc110_start_otg;
+ otg_data->stop = s5pc110_stop_otg;
+ otg_data->pdev = pdev;
+
+ return 0;
+}
+
+
+/**
+ * static int s5pc110_otg_drv_remove (struct platform_device *dev)
+ *
+ * @brief remove function of OTG hcd platform_driver
+ *
+ * @param [in] pdev : pointer of platform_device of otg hcd platform_driver
+ *
+ * @return USB_ERR_SUCCESS : If success
+ * USB_ERR_FAIL : If fail
+ * @remark
+ * This function is called when the otg device unregistered with the
+ * s5pc110_otg_driver. This happens, for example, when the rmmod command is
+ * executed. The device may or may not be electrically present. If it is
+ * present, the driver stops device processing. Any resources used on behalf
+ * of this device are freed.
+ */
+
+static int s5pc110_otg_drv_remove(struct platform_device *pdev)
+{
+ struct sec_otghost_data *otg_data = dev_get_platdata(&pdev->dev);
+
+ clk_put(otg_data->clk);
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * @struct s5pc110_otg_driver
+ *
+ * @brief
+ * This structure defines the methods to be called by a bus driver
+ * during the lifecycle of a device on that bus. Both drivers and
+ * devices are registered with a bus driver. The bus driver matches
+ * devices to drivers based on information in the device and driver
+ * structures.
+ *
+ * The probe function is called when the bus driver matches a device
+ * to this driver. The remove function is called when a device is
+ * unregistered with the bus driver.
+ */
+struct platform_driver s5pc110_otg_driver = {
+ .probe = s5pc110_otg_drv_probe,
+ .remove = s5pc110_otg_drv_remove,
+/* .shutdown = usb_hcd_platform_shutdown, */
+ .driver = {
+ .name = "s3c_otghcd",
+ .owner = THIS_MODULE,
+ },
+};
+
+/**
+ * static int __init s5pc110_otg_module_init(void)
+ *
+ * @brief module_init function
+ *
+ * @return it returns result of platform_driver_register
+ * @remark
+ * This function is called when the s5pc110_otg_driver is installed with the
+ * insmod command. It registers the s5pc110_otg_driver structure with the
+ * appropriate bus driver. This will cause the s5pc110_otg_driver_probe function
+ * to be called. In addition, the bus driver will automatically expose
+ * attributes defined for the device and driver in the special sysfs file
+ * system.
+ */
+static int __init s5pc110_otg_module_init(void)
+{
+ int ret_val = 0;
+
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "s3c_otg_module_init\n");
+
+ ret_val = platform_driver_register(&s5pc110_otg_driver);
+ if (ret_val < 0) {
+ otg_err(OTG_DBG_OTGHCDI_DRIVER,
+ "platform_driver_register\n");
+ }
+ return ret_val;
+}
+
+/**
+ * static void __exit s5pc110_otg_module_exit(void)
+ *
+ * @brief module_exit function
+ *
+ * @remark
+ * This function is called when the driver is removed from the kernel
+ * with the rmmod command. The driver unregisters itself with its bus
+ * driver.
+ */
+static void __exit s5pc110_otg_module_exit(void)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "s3c_otg_module_exit\n");
+ platform_driver_unregister(&s5pc110_otg_driver);
+}
+
+/* for debug */
+void otg_print_registers(void)
+{
+ /* USB PHY Control Registers */
+
+ pr_info("otg clock = %s\n",
+ (readl(OTG_CLOCK) & (1<<13)) ? "ON" : "OFF");
+ pr_info("otg USB_CONTROL = 0x%x.\n", readl(OTG_PHY_CONTROL));
+ pr_info("otg UPHYPWR = 0x%x.\n", readl(OTG_PHYPWR));
+ pr_info("otg UPHYCLK = 0x%x.\n", readl(OTG_PHYCLK));
+ pr_info("otg URSTCON = 0x%x.\n", readl(OTG_RSTCON));
+
+ /* OTG LINK Core registers (Core Global Registers) */
+ pr_info("otg GOTGCTL = 0x%x.\n", read_reg_32(GOTGCTL));
+ pr_info("otg GOTGINT = 0x%x.\n", read_reg_32(GOTGINT));
+ pr_info("otg GAHBCFG = 0x%x.\n", read_reg_32(GAHBCFG));
+ pr_info("otg GUSBCFG = 0x%x.\n", read_reg_32(GUSBCFG));
+ pr_info("otg GINTSTS = 0x%x.\n", read_reg_32(GINTSTS));
+ pr_info("otg GINTMSK = 0x%x.\n", read_reg_32(GINTMSK));
+
+ /* Host Mode Registers */
+ pr_info("otg HCFG = 0x%x.\n", read_reg_32(HCFG));
+ pr_info("otg HPRT = 0x%x.\n", read_reg_32(HPRT));
+ pr_info("otg HFIR = 0x%x.\n", read_reg_32(HFIR));
+
+ /* Synopsys ID */
+ pr_info("otg GSNPSID = 0x%x.\n", read_reg_32(GSNPSID));
+
+ /* HWCFG */
+ pr_info("otg GHWCFG1 = 0x%x.\n", read_reg_32(GHWCFG1));
+ pr_info("otg GHWCFG2 = 0x%x.\n", read_reg_32(GHWCFG2));
+ pr_info("otg GHWCFG3 = 0x%x.\n", read_reg_32(GHWCFG3));
+ pr_info("otg GHWCFG4 = 0x%x.\n", read_reg_32(GHWCFG4));
+
+ /* PCGCCTL */
+ pr_info("otg PCGCCTL = 0x%x.\n", read_reg_32(PCGCCTL));
+}
+
+late_initcall(s5pc110_otg_module_init);
+module_exit(s5pc110_otg_module_exit);
+
+MODULE_DESCRIPTION("OTG USB HOST controller driver");
+MODULE_AUTHOR("SAMSUNG / System LSI / EMSP");
+MODULE_LICENSE("GPL");