USB: dwc3: msm: fix mmap to allow only charger block register address space

/dev/usb_ext_chg device file interface supports mmap and its current
implementation allows any address for mapping.  Fix it by mapping only
charger block register address space.  The user space does not need to
pass any physical address in mmap.  Implement an ioctl to tell the
offset from which the first charger block register is available.  User
space adds this offset to the virtual address returned by mmap to access
charger detection registers.

The MSM_USB_EXT_CHG_INFO ioctl also returns the charger block type
(QSCRATCH/ULPI) to user space.  Use the ioctl interface for blocking
low power mode while charger detection takes place in user space.

Change-Id: Ie27988e1989564124b40cf7c40c8eb67dac99c65
Signed-off-by: Pavankumar Kondeti <pkondeti@codeaurora.org>
diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c
index 9446ddf..2487e3f 100644
--- a/drivers/usb/dwc3/dwc3-msm.c
+++ b/drivers/usb/dwc3/dwc3-msm.c
@@ -34,6 +34,7 @@
 #include <linux/usb/gadget.h>
 #include <linux/qpnp-misc.h>
 #include <linux/usb/msm_hsusb.h>
+#include <linux/usb/msm_ext_chg.h>
 #include <linux/regulator/consumer.h>
 #include <linux/power_supply.h>
 #include <linux/qpnp/qpnp-adc.h>
@@ -165,7 +166,7 @@
 struct dwc3_msm {
 	struct device *dev;
 	void __iomem *base;
-	u32 resource_size;
+	struct resource *io_res;
 	int dbm_num_eps;
 	u8 ep_num_mapping[DBM_MAX_EPS];
 	const struct usb_ep_ops *original_ep_ops[DWC3_ENDPOINTS_NUM];
@@ -2400,47 +2401,68 @@
 	return 0;
 }
 
-static ssize_t
-dwc3_msm_ext_chg_write(struct file *file, const char __user *ubuf,
-				size_t size, loff_t *pos)
+static long
+dwc3_msm_ext_chg_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 {
 	struct dwc3_msm *mdwc = context;
-	char kbuf[16];
+	struct msm_usb_chg_info info = {0};
+	int ret = 0, val;
 
-	memset(kbuf, 0x00, sizeof(kbuf));
-	if (copy_from_user(&kbuf, ubuf, min_t(size_t, sizeof(kbuf) - 1, size)))
-		return -EFAULT;
+	switch (cmd) {
+	case MSM_USB_EXT_CHG_INFO:
+		info.chg_block_type = USB_CHG_BLOCK_QSCRATCH;
+		info.page_offset = (context->io_res->start +
+				QSCRATCH_REG_OFFSET) & ~PAGE_MASK;
+		/*
+		 * The charger block register address space is only
+		 * 512 bytes.  But mmap() works on PAGE granularity.
+		 */
+		info.length = PAGE_SIZE;
 
-	pr_debug("%s: buf = %s\n", __func__, kbuf);
-
-	if (!strncmp(kbuf, "enable", 6)) {
-		pr_info("%s: on\n", __func__);
-		if (mdwc->charger.chg_type == DWC3_DCP_CHARGER) {
-			pm_runtime_get_sync(mdwc->dev);
+		if (copy_to_user((void __user *)arg, &info, sizeof(info))) {
+			pr_err("%s: copy to user failed\n\n", __func__);
+			ret = -EFAULT;
+		}
+		break;
+	case MSM_USB_EXT_CHG_BLOCK_LPM:
+		if (get_user(val, (int __user *)arg)) {
+			pr_err("%s: get_user failed\n\n", __func__);
+			ret = -EFAULT;
+			break;
+		}
+		pr_debug("%s: LPM block request %d\n", __func__, val);
+		if (val) { /* block LPM */
+			if (mdwc->charger.chg_type == DWC3_DCP_CHARGER) {
+				pm_runtime_get_sync(mdwc->dev);
+			} else {
+				mdwc->ext_chg_active = false;
+				complete(&mdwc->ext_chg_wait);
+				ret = -ENODEV;
+			}
 		} else {
 			mdwc->ext_chg_active = false;
 			complete(&mdwc->ext_chg_wait);
-			return -ENODEV;
+			pm_runtime_put(mdwc->dev);
 		}
-	} else if (!strncmp(kbuf, "disable", 7)) {
-		pr_info("%s: off\n", __func__);
-		mdwc->ext_chg_active = false;
-		complete(&mdwc->ext_chg_wait);
-		pm_runtime_put(mdwc->dev);
-	} else {
-		return -EINVAL;
+		break;
+	default:
+		ret = -EINVAL;
 	}
 
-	return size;
+	return ret;
 }
 
 static int dwc3_msm_ext_chg_mmap(struct file *file, struct vm_area_struct *vma)
 {
+	struct dwc3_msm *mdwc = context;
 	unsigned long vsize = vma->vm_end - vma->vm_start;
 	int ret;
 
-	pr_debug("%s: size = %lu %x\n", __func__, vsize, (int) vma->vm_pgoff);
+	if (vma->vm_pgoff != 0 || vsize > PAGE_SIZE)
+		return -EINVAL;
 
+	vma->vm_pgoff = __phys_to_pfn(mdwc->io_res->start +
+				QSCRATCH_REG_OFFSET);
 	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
 
 	ret = io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
@@ -2465,7 +2487,7 @@
 static const struct file_operations dwc3_msm_ext_chg_fops = {
 	.owner = THIS_MODULE,
 	.open = dwc3_msm_ext_chg_open,
-	.write = dwc3_msm_ext_chg_write,
+	.unlocked_ioctl = dwc3_msm_ext_chg_ioctl,
 	.mmap = dwc3_msm_ext_chg_mmap,
 	.release = dwc3_msm_ext_chg_release,
 };
@@ -2799,7 +2821,7 @@
 		goto disable_hs_ldo;
 	}
 
-	msm->resource_size = resource_size(res);
+	msm->io_res = res; /* used to calculate chg block offset */
 
 	if (of_property_read_u32(node, "qcom,dwc-hsphy-init",
 						&msm->hsphy_init_seq))
diff --git a/include/linux/usb/Kbuild b/include/linux/usb/Kbuild
index b607f35..087d163 100644
--- a/include/linux/usb/Kbuild
+++ b/include/linux/usb/Kbuild
@@ -5,6 +5,7 @@
 header-y += functionfs.h
 header-y += gadgetfs.h
 header-y += midi.h
+header-y += msm_ext_chg.h
 header-y += g_printer.h
 header-y += tmc.h
 header-y += video.h
diff --git a/include/linux/usb/msm_ext_chg.h b/include/linux/usb/msm_ext_chg.h
new file mode 100644
index 0000000..dcc786d
--- /dev/null
+++ b/include/linux/usb/msm_ext_chg.h
@@ -0,0 +1,31 @@
+#ifndef __LINUX_USB_MSM_EXT_CHG_H
+#define __LINUX_USB_MSM_EXT_CHG_H
+
+#include <linux/ioctl.h>
+
+#define USB_CHG_BLOCK_ULPI	1
+#define USB_CHG_BLOCK_QSCRATCH	2
+
+/**
+ * struct msm_usb_chg_info - MSM USB charger block details.
+ * @chg_block_type: The type of charger block. QSCRATCH/ULPI.
+ * @page_offset: USB charger register base may not be aligned to
+ *              PAGE_SIZE.  The kernel driver aligns the base
+ *              address and use it for memory mapping.  This
+ *              page_offset is used by user space to calaculate
+ *              the corret charger register base address.
+ * @length: The length of the charger register address space.
+ */
+struct msm_usb_chg_info {
+	uint32_t chg_block_type;
+	off_t page_offset;
+	size_t length;
+};
+
+/* Get the MSM USB charger block information */
+#define MSM_USB_EXT_CHG_INFO _IOW('M', 0, struct msm_usb_chg_info)
+
+/* Vote against USB hardware low power mode */
+#define MSM_USB_EXT_CHG_BLOCK_LPM _IOW('M', 1, int)
+
+#endif /* __LINUX_USB_MSM_EXT_CHG_H */