ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/marvell/linux/drivers/misc/microsemi-slic.c b/marvell/linux/drivers/misc/microsemi-slic.c
new file mode 100644
index 0000000..d17620c
--- /dev/null
+++ b/marvell/linux/drivers/misc/microsemi-slic.c
@@ -0,0 +1,463 @@
+/*
+ * ASRMicro SLIC driver
+ *
+ * Copyright (C) 2020 ASR Microelectronic Ltd.
+ *
+ * Author: Jackie Fan <yuanchunfan@asrmicro.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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/gpio.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/delay.h>
+#include <linux/regulator/machine.h>
+#include <linux/platform_device.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/edge_wakeup_mmp.h>
+#include <linux/mfd/88pm80x.h>
+
+#define SLIC_STATUS_LENS 8
+
+static int g_reset_n_gpio = -1;
+static int g_edge_wakeup_gpio = -1;
+static int g_vdd_3v3_gpio = -1;
+
+struct microsemi_slic_info {
+	struct device *dev;
+	char chip_status[SLIC_STATUS_LENS];
+	char reset_status[SLIC_STATUS_LENS];
+	int reset_n_gpio;
+	int edge_wakeup_gpio;
+	int vdd_3v3_gpio;
+	struct pinctrl *pinctrl;
+	struct pinctrl_state *pin_lpm_drv_low;
+	struct pinctrl_state *pin_lpm_drv_high;
+	struct regulator *vdd_3v3;
+};
+
+static void slic_power_on(struct microsemi_slic_info *info)
+{
+	if (!strncmp(info->chip_status, "on", 2)) {
+		dev_info(info->dev, "slic chip already powered on\n");
+		return;
+	}
+
+	if (info->vdd_3v3 > 0) {
+		if (regulator_set_voltage(info->vdd_3v3, 3300000, 3300000))
+			pr_err("fail to set regulator wib_3v3 supply 2 to 3.3v\n");
+		if (regulator_enable(info->vdd_3v3))
+			pr_err("fail to enable regulator vdd_3v3\n");
+	}
+
+	if (info->vdd_3v3_gpio >= 0) {
+		gpio_direction_output(info->vdd_3v3_gpio, 1);
+	}
+
+	usleep_range(65, 70);
+
+	if (info->reset_n_gpio >= 0) {
+		gpio_direction_output(info->reset_n_gpio, 1);
+		strncpy(info->reset_status, "high", 5);
+	}
+
+	strncpy(info->chip_status, "on", 3);
+
+	if (info->vdd_3v3 > 0) {
+		buck2_ldo8_sleepmode_control_for_slic(1);
+	}
+
+	dev_info(info->dev, "slic chip powered on\n");
+}
+
+static void slic_power_off(struct microsemi_slic_info *info)
+{
+	if (!strncmp(info->chip_status, "off", 3)) {
+		dev_info(info->dev, "slic chip already powered off\n");
+		return;
+	}
+
+	if (info->vdd_3v3 > 0) {
+		if (regulator_disable(info->vdd_3v3))
+			pr_err("fail to disable regulator vdd_3v3\n");
+	}
+
+	if (info->vdd_3v3_gpio >= 0) {
+		gpio_direction_output(info->vdd_3v3_gpio, 0);
+	}
+
+	if (info->reset_n_gpio >= 0) {
+		gpio_direction_output(info->reset_n_gpio, 0);
+		strncpy(info->reset_status, "low", 4);
+	}
+
+	strncpy(info->chip_status, "off", 4);
+
+	if (info->vdd_3v3 > 0) {
+		buck2_ldo8_sleepmode_control_for_slic(0);
+	}
+
+	dev_info(info->dev, "slic chip powered off\n");
+}
+
+static void slic_reset(struct microsemi_slic_info *info, int flag)
+{
+	if (info->reset_n_gpio >= 0) {
+		gpio_direction_output(info->reset_n_gpio, flag);
+		sprintf(info->reset_status, "%s", flag ? "high" : "low");
+		dev_info(info->dev, "slic chip reset_n set to %s\n", flag ? "high" : "low");
+	}
+}
+
+static ssize_t slic_ctrl(struct device *dev,
+			struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	static char msg[64];
+	int flag, ret;
+
+	struct microsemi_slic_info *info = dev_get_drvdata(dev);
+
+	count = (count > 64) ? 64 : count;
+	memset(msg, 0, count);
+
+	if (!strncmp(buf, "off", 3)) {
+		slic_power_off(info);
+	} else if (!strncmp(buf, "on", 2)) {
+		slic_power_on(info);
+	} else if (!strncmp(buf, "reset", 5)) {
+		ret = sscanf(buf, "%s %d", msg, &flag);
+		if (ret == 2)
+			slic_reset(info, flag);
+	} else
+		dev_info(info->dev, "usage wrong\n");
+
+	return count;
+}
+
+static ssize_t slic_status(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct microsemi_slic_info *info = dev_get_drvdata(dev);
+
+	return sprintf(buf, "power: %s, reset_n: %s\n", info->chip_status,
+		info->reset_status);
+}
+
+static DEVICE_ATTR(ctrl, S_IWUSR, slic_status, slic_ctrl);
+static DEVICE_ATTR(status, S_IRUSR, slic_status, NULL);
+
+static const struct attribute *slic_attrs[] = {
+	&dev_attr_ctrl.attr,
+	&dev_attr_status.attr,
+	NULL,
+};
+
+static const struct attribute_group slic_attr_group = {
+	.attrs = (struct attribute **)slic_attrs,
+};
+
+#ifdef CONFIG_OF
+static int microsemi_slic_dt_init(struct device_node *np,
+			   struct device *dev,
+			   struct microsemi_slic_info *info)
+{
+
+	struct regulator *vdd_3v3 = NULL;
+
+	info->reset_n_gpio =
+		of_get_named_gpio(dev->of_node, "rst-gpio", 0);
+	if (info->reset_n_gpio < 0) {
+		dev_err(dev, "%s: of_get_named_gpio failed: %d\n", __func__,
+		       info->reset_n_gpio);
+		info->reset_n_gpio = -1;
+	}
+
+	info->edge_wakeup_gpio =
+		of_get_named_gpio(dev->of_node, "edge-wakeup-gpio", 0);
+	if (info->edge_wakeup_gpio < 0) {
+		dev_err(dev, "%s: of_get_named_gpio failed: %d\n", __func__,
+		       info->edge_wakeup_gpio);
+		info->edge_wakeup_gpio = -1;
+		goto out;
+	}
+
+	info->vdd_3v3_gpio = of_get_named_gpio(dev->of_node, "vdd-3v3-gpio", 0);
+	if (info->vdd_3v3_gpio < 0) {
+		dev_err(dev, "%s: of_get_named_gpio failed: %d\n", __func__, info->vdd_3v3_gpio);
+		info->vdd_3v3_gpio = -1;
+	}
+
+	g_reset_n_gpio = info->reset_n_gpio;
+	g_edge_wakeup_gpio = info->edge_wakeup_gpio;
+	g_vdd_3v3_gpio = info->vdd_3v3_gpio;
+
+	printk(KERN_INFO"%s: reset_n_gpio=%d, edge_wakeup_gpio=%d, vdd_3v3_gpio=%d, %s.\n",
+                         __FUNCTION__, info->reset_n_gpio, info->edge_wakeup_gpio, info->vdd_3v3_gpio, (info->vdd_3v3_gpio < 0)?"ASR1826 Old DKB":"ASR1826 New DKB");
+
+	printk(KERN_INFO"%s: g_reset_n_gpio=%d, g_edge_wakeup_gpio=%d, g_vdd_3v3_gpio=%d.\n",
+                         __FUNCTION__, g_reset_n_gpio, g_edge_wakeup_gpio, g_vdd_3v3_gpio);
+
+	vdd_3v3 = regulator_get(dev, "vdd33");
+	if (IS_ERR_OR_NULL(vdd_3v3)) {
+		if (PTR_ERR(vdd_3v3) < 0) {
+			pr_info("%s: the regulator for vdd_3v3 not found\n", __func__);
+		}
+	} else {
+		info->vdd_3v3 = vdd_3v3;
+	}
+
+	return 0;
+out:
+	return -EINVAL;
+}
+#else
+static int microsemi_slic_dt_init(struct device_node *np,
+			   struct device *dev,
+			   struct microsemi_slic_info *info)
+{
+	return 0;
+}
+#endif
+
+/* microsemi_slic_ctrl */
+static struct dentry *microsemi_slic_ctrl = NULL;
+
+static ssize_t microsemi_slic_ctrl_read(struct file *file, char __user *user_buf,
+        size_t count, loff_t *ppos)
+{
+
+    printk(KERN_INFO"%s/L%d.\n", __FUNCTION__, __LINE__);
+
+    return 0;
+}
+
+/*
+ *control command:
+ *
+ *Disable VDD 3V3: echo 0 > /sys/kernel/debug/microsemi_slic_ctrl
+ *Enable VDD 3V3: echo 1 > /sys/kernel/debug/microsemi_slic_ctrl
+ *
+ *
+ */
+static char msg[10];
+
+static ssize_t microsemi_slic_ctrl_write(struct file *file,
+        const char __user *user_buf,
+        size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    size_t tmp_count = 0;
+
+    printk(KERN_INFO"%s/L%d.\n", __FUNCTION__, __LINE__);
+
+    memset(msg, 0x00, sizeof(msg));
+    tmp_count = count;
+
+    if (tmp_count >= sizeof(msg)){
+        tmp_count = sizeof(msg) - 1;
+    }
+
+    /* copy the content from user space to kernel space */
+    ret = copy_from_user(msg, user_buf, tmp_count);
+    if (ret){
+        printk(KERN_ALERT"copy from user fail \n");
+        return -EFAULT;
+    }
+
+    switch (msg[0]){
+        case '0':/* input command# echo 0 > /sys/kernel/debug/microsemi_slic_ctrl */
+            printk(KERN_INFO "input %c. \n", msg[0]);
+            if (g_vdd_3v3_gpio >= 0) {
+                gpio_direction_output(g_vdd_3v3_gpio, 0);
+            }
+            printk(KERN_INFO "Disable VDD 3V3 for ASR1826 New DKB.\n");
+            break;
+
+        case '1':/* input command# echo 1 > /sys/kernel/debug/microsemi_slic_ctrl */
+            printk(KERN_INFO "input %c. \n", msg[0]);
+            if (g_vdd_3v3_gpio >= 0) {
+                gpio_direction_output(g_vdd_3v3_gpio, 1);
+            }
+            printk(KERN_INFO "Enable VDD 3V3 for ASR1826 New DKB.\n");
+            break;
+
+        case '2':/* input command# echo 2 > /sys/kernel/debug/microsemi_slic_ctrl */
+            printk(KERN_INFO "input %c. \n", msg[0]);
+            break;
+
+        default:/* input command#  */
+            printk(KERN_INFO "input invalid. \n");
+            break;
+    }
+
+    return tmp_count;
+}
+
+static const struct file_operations microsemi_slic_ctrl_ops = {
+    .owner      = THIS_MODULE,
+    .open       = simple_open,
+    .read       = microsemi_slic_ctrl_read,
+    .write      = microsemi_slic_ctrl_write,
+};
+
+static inline int microsemi_slic_ctrl_debugfs_init(void)
+{
+
+    microsemi_slic_ctrl = debugfs_create_file("microsemi_slic_ctrl", S_IRUGO | S_IFREG,
+            NULL, NULL, &microsemi_slic_ctrl_ops);
+
+    if (microsemi_slic_ctrl == NULL) {
+        pr_err("create microsemi_slic_ctrl debugfs error!\n");
+        return -ENOENT;
+    } else if (microsemi_slic_ctrl == ERR_PTR(-ENODEV)) {
+        pr_err("CONFIG_DEBUG_FS is not enabled!\n");
+        return -ENOENT;
+    }
+
+    return 0;
+}
+
+static void microsemi_slic_ctrl_debugfs_remove(void)
+{
+    if (NULL != microsemi_slic_ctrl){
+        debugfs_remove_recursive(microsemi_slic_ctrl);
+    }
+
+    return;
+}
+
+static int microsemi_slic_probe(struct platform_device *pdev)
+{
+	struct microsemi_slic_info *info;
+
+	struct device_node *np = pdev->dev.of_node;
+	int ret;
+
+	printk(KERN_INFO "enter %s. \n", __FUNCTION__);
+
+	info = devm_kzalloc(&pdev->dev, sizeof(struct microsemi_slic_info),
+						GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	memset(info, 0x00, sizeof(struct microsemi_slic_info));
+	info->reset_n_gpio = -1;
+	info->edge_wakeup_gpio = -1;
+	info->vdd_3v3_gpio = -1;
+
+	if (IS_ENABLED(CONFIG_OF)) {
+		ret = microsemi_slic_dt_init(np, &pdev->dev, info);
+		if (ret) {
+			dev_err(&pdev->dev, "SLIC probe failed!\n");
+			return ret;
+		}
+	} else {
+		dev_err(&pdev->dev, "SLIC Not support DT, exit!\n");
+		return -EINVAL;
+	}
+
+	info->dev = &pdev->dev;
+
+	if (info->reset_n_gpio >= 0) {
+		ret = devm_gpio_request(info->dev,
+				info->reset_n_gpio, "slic_rst");
+		if (ret) {
+			dev_err(info->dev,
+				"request gpio %d failed\n", info->reset_n_gpio);
+			return ret;
+		}
+	}
+
+	/* use microsemi-slic as wakeup source */
+	device_init_wakeup(&pdev->dev, 1);
+
+	if (info->edge_wakeup_gpio >= 0) {
+		ret = request_mfp_edge_wakeup(info->edge_wakeup_gpio,
+					      NULL, info, info->dev);
+		if (ret) {
+			dev_err(info->dev, "failed to request edge wakeup.\n");
+			remove_mfp_edge_wakeup(info->edge_wakeup_gpio);
+			return ret;
+		}
+	}
+
+	if (info->vdd_3v3_gpio >= 0) {
+		ret = devm_gpio_request(info->dev, info->vdd_3v3_gpio, "vdd_3v3");
+		if (ret) {
+			dev_err(info->dev, "request gpio %d failed\n", info->vdd_3v3_gpio);
+			return ret;
+		}
+	}
+
+	slic_power_on(info);
+	strncpy(info->chip_status, "on", 3);
+
+	platform_set_drvdata(pdev, info);
+
+	ret = sysfs_create_group(&pdev->dev.kobj, &slic_attr_group);
+	if (ret) {
+		dev_err(&pdev->dev, "SLIC create sysfs fail!\n");
+		return ret;
+	}
+
+	/* init debug tool */
+	microsemi_slic_ctrl_debugfs_init();
+	return 0;
+}
+
+static int microsemi_slic_remove(struct platform_device *pdev)
+{
+	printk(KERN_INFO "enter %s. \n", __FUNCTION__);
+
+	sysfs_remove_group(&pdev->dev.kobj, &slic_attr_group);
+
+	microsemi_slic_ctrl_debugfs_remove();
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id microsemi_slic_dt_match[] = {
+	{ .compatible = "asr,microsemi-slic", },
+	{},
+};
+#endif
+
+static struct platform_driver microsemi_slic_driver = {
+	.driver		= {
+		.name	= "microsemi-slic",
+		.owner	= THIS_MODULE,
+#ifdef CONFIG_OF
+		.of_match_table = of_match_ptr(microsemi_slic_dt_match),
+#endif
+	},
+	.probe		= microsemi_slic_probe,
+	.remove		= microsemi_slic_remove,
+};
+
+static int microsemi_slic_init(void)
+{
+	return platform_driver_register(&microsemi_slic_driver);
+}
+
+static void microsemi_slic_exit(void)
+{
+	platform_driver_unregister(&microsemi_slic_driver);
+}
+module_init(microsemi_slic_init);
+module_exit(microsemi_slic_exit);
+
+MODULE_AUTHOR("Jackie Fan<yuanchunfan@asrmicro.com>");
+MODULE_DESCRIPTION("driver for Microsemi SLIC solution");
+MODULE_LICENSE("GPL v2");