| /* |
| * Marvell SLIC driver |
| * |
| * Copyright (C) 2014 Marvell Technology Group Ltd. |
| * |
| * Author: Yu Zhang <zhangy@marvell.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> |
| #include <soc/asr/addr-map.h> |
| |
| #define SLIC_STATUS_LENS 8 |
| |
| struct nz3_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 struct pinctrl *g_nz3_slic_pinctrl = NULL; |
| |
| static int g_reset_n_gpio = -1; |
| static int g_edge_wakeup_gpio = -1; |
| static int g_vdd_3v3_gpio = -1; |
| |
| //#define DEBUG_FALCON_SLIC_RESET |
| #ifdef DEBUG_FALCON_SLIC_RESET |
| |
| #define MFPR_VIRT_BASE (APB_VIRT_BASE + 0x1e000) |
| |
| static void __iomem *APBC_AIB_CLK_RST_reg = APB_VIRT_BASE + 0x15000 + 0x3C;/* 0xD4015000+0x3C */ |
| static void __iomem *GPIO10_MFPR_reg = MFPR_VIRT_BASE + 0x104;/* 0xD401E000+0x104 */ |
| static void __iomem *APBC_GPIO_CLK_RST_reg = APB_VIRT_BASE + 0x15000 + 0x8;/* 0xD4015008 */ |
| static void __iomem *GPIO_PDR0_reg = APB_VIRT_BASE + 0x19000 +0xC;/* 0xD4019000+0xC */ |
| #if 0 |
| static void __iomem *GPIO_PSR0_reg = APB_VIRT_BASE + 0x19000 +0x18;/* 0xD4019000+0x18 */ |
| static void __iomem *GPIO_PCR0_reg = APB_VIRT_BASE + 0x19000 +0x24;/* 0xD4019000+0x24 */ |
| #endif |
| |
| static void falcon_config_MFPR_GPIO10(void) |
| { |
| int reg_value = 0; |
| |
| printk(KERN_INFO"%s:L%d, enter.\n", __FUNCTION__, __LINE__); |
| |
| if (!APBC_AIB_CLK_RST_reg ||!GPIO10_MFPR_reg ||!APBC_GPIO_CLK_RST_reg || !GPIO_PDR0_reg) |
| { |
| printk(KERN_INFO"%s:L%d, NULL pointer.\n", __FUNCTION__, __LINE__); |
| return; |
| } |
| |
| //*(volatile unsigned int *)APBC_AIB_CLK_RST_reg = 0xFFFFFFFB;/* open aib MFPR config clock/rst */ |
| __raw_writel(0xFFFFFFFB, APBC_AIB_CLK_RST_reg); |
| |
| //*(volatile unsigned int *)GPIO10_MFPR_reg &= ~((0x3<<13) | 0x7);/* use function 0 to select gpio_in[10], and NO Pull up/dn */ |
| reg_value = __raw_readl(GPIO10_MFPR_reg); |
| printk(KERN_INFO"%s,GPIO10_MFPR_reg value before= 0x%x\n", __FUNCTION__, reg_value); |
| reg_value &= ~((0x3<<13) | 0x7); |
| __raw_writel(reg_value, GPIO10_MFPR_reg);/* Falcon */ |
| printk(KERN_INFO"%s,GPIO10_MFPR_reg value after= 0x%x\n", __FUNCTION__, reg_value); |
| |
| //*(volatile unsigned int *)APBC_GPIO_CLK_RST_reg = 0x3;/* gpio clock/rst enable */ |
| __raw_writel(0x3, APBC_GPIO_CLK_RST_reg); |
| |
| //*(volatile unsigned int *)GPIO_PDR0_reg |= 0x1<<10;/* gpio_in[10] output */ |
| reg_value = __raw_readl(GPIO_PDR0_reg); |
| reg_value |= 0x1<<10; |
| __raw_writel(reg_value, GPIO_PDR0_reg);/* Falcon */ |
| |
| return; |
| } |
| #endif |
| |
| static void slic_power_on(struct nz3_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); |
| #ifdef DEBUG_FALCON_SLIC_RESET |
| if (cpu_is_asr1803()) { |
| falcon_config_MFPR_GPIO10(); |
| } |
| #endif |
| 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 nz3_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); |
| } |
| #ifdef DEBUG_FALCON_SLIC_RESET |
| if (cpu_is_asr1803()) { |
| falcon_config_MFPR_GPIO10(); |
| } |
| #endif |
| 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 nz3_slic_info *info, int flag) |
| { |
| printk(KERN_INFO "%s, reset_n_gpio=%d, flag=%d.\n", __FUNCTION__, info->reset_n_gpio, flag); |
| #ifdef DEBUG_FALCON_SLIC_RESET |
| if (cpu_is_asr1803()) { |
| falcon_config_MFPR_GPIO10(); |
| } |
| #endif |
| 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 nz3_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 nz3_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 nz3_slic_dt_init(struct device_node *np, |
| struct device *dev, |
| struct nz3_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; |
| goto out; |
| } |
| |
| 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 nz3_slic_dt_init(struct device_node *np, |
| struct device *dev, |
| struct nz3_slic_info *info) |
| { |
| return 0; |
| } |
| #endif |
| |
| /* nz3_slic_ctrl */ |
| static struct dentry *nz3_slic_ctrl = NULL; |
| |
| static ssize_t nz3_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/nz3_slic_ctrl |
| *Enable VDD 3V3: echo 1 > /sys/kernel/debug/nz3_slic_ctrl |
| * |
| * |
| */ |
| static char msg[10]; |
| |
| static ssize_t nz3_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/nz3_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/nz3_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/nz3_slic_ctrl */ |
| printk(KERN_INFO "input %c. \n", msg[0]); |
| #ifdef DEBUG_FALCON_SLIC_RESET |
| if (cpu_is_asr1803()) { |
| falcon_config_MFPR_GPIO10(); |
| } |
| #endif |
| if (g_reset_n_gpio >= 0) { |
| gpio_direction_output(g_reset_n_gpio, 0); |
| gpio_set_value(g_reset_n_gpio, 0); |
| } |
| printk(KERN_INFO "low reset pin, g_reset_n_gpio=%d.\n", g_reset_n_gpio); |
| break; |
| |
| case '3':/* input command# echo 3 > /sys/kernel/debug/nz3_slic_ctrl */ |
| printk(KERN_INFO "input %c. \n", msg[0]); |
| #ifdef DEBUG_FALCON_SLIC_RESET |
| if (cpu_is_asr1803()) { |
| falcon_config_MFPR_GPIO10(); |
| } |
| #endif |
| if (g_reset_n_gpio >= 0) { |
| gpio_direction_output(g_reset_n_gpio, 1); |
| gpio_set_value(g_reset_n_gpio, 1); |
| } |
| printk(KERN_INFO "high reset pin, g_reset_n_gpio=%d.\n", g_reset_n_gpio); |
| break; |
| |
| default:/* input command# */ |
| printk(KERN_INFO "input invalid. \n"); |
| break; |
| } |
| |
| return tmp_count; |
| } |
| |
| static const struct file_operations nz3_slic_ctrl_ops = { |
| .owner = THIS_MODULE, |
| .open = simple_open, |
| .read = nz3_slic_ctrl_read, |
| .write = nz3_slic_ctrl_write, |
| }; |
| |
| static inline int nz3_slic_ctrl_debugfs_init(void) |
| { |
| |
| nz3_slic_ctrl = debugfs_create_file("nz3_slic_ctrl", S_IRUGO | S_IFREG, |
| NULL, NULL, &nz3_slic_ctrl_ops); |
| |
| if (nz3_slic_ctrl == NULL) { |
| pr_err("create nz3_slic_ctrl debugfs error!\n"); |
| return -ENOENT; |
| } else if (nz3_slic_ctrl == ERR_PTR(-ENODEV)) { |
| pr_err("CONFIG_DEBUG_FS is not enabled!\n"); |
| return -ENOENT; |
| } |
| |
| return 0; |
| } |
| |
| static void nz3_slic_ctrl_debugfs_remove(void) |
| { |
| if (NULL != nz3_slic_ctrl){ |
| debugfs_remove_recursive(nz3_slic_ctrl); |
| } |
| |
| return; |
| } |
| |
| static int nz3_slic_probe(struct platform_device *pdev) |
| { |
| struct nz3_slic_info *info = NULL; |
| struct pinctrl_state *pin_default_nz3_slic = NULL; |
| |
| struct device_node *np = pdev->dev.of_node; |
| int ret; |
| |
| printk(KERN_INFO "enter %s. \n", __FUNCTION__); |
| |
| info = devm_kzalloc(&pdev->dev, sizeof(struct nz3_slic_info), |
| GFP_KERNEL); |
| if (!info) |
| return -ENOMEM; |
| |
| memset(info, 0x00, sizeof(struct nz3_slic_info)); |
| info->reset_n_gpio = -1; |
| info->edge_wakeup_gpio = -1; |
| info->vdd_3v3_gpio = -1; |
| |
| g_nz3_slic_pinctrl = devm_pinctrl_get(&pdev->dev); |
| pin_default_nz3_slic = pinctrl_lookup_state(g_nz3_slic_pinctrl, "default"); |
| pinctrl_select_state(g_nz3_slic_pinctrl, pin_default_nz3_slic); |
| |
| if (IS_ENABLED(CONFIG_OF)) { |
| ret = nz3_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 nz3-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 */ |
| nz3_slic_ctrl_debugfs_init(); |
| return 0; |
| } |
| |
| static int nz3_slic_remove(struct platform_device *pdev) |
| { |
| printk(KERN_INFO "enter %s. \n", __FUNCTION__); |
| |
| sysfs_remove_group(&pdev->dev.kobj, &slic_attr_group); |
| |
| nz3_slic_ctrl_debugfs_remove(); |
| return 0; |
| } |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id nz3_slic_dt_match[] = { |
| { .compatible = "asr,nz3-slic", }, |
| {}, |
| }; |
| #endif |
| |
| static struct platform_driver nz3_slic_driver = { |
| .driver = { |
| .name = "nz3-slic", |
| .owner = THIS_MODULE, |
| #ifdef CONFIG_OF |
| .of_match_table = of_match_ptr(nz3_slic_dt_match), |
| #endif |
| }, |
| .probe = nz3_slic_probe, |
| .remove = nz3_slic_remove, |
| }; |
| |
| static int nz3_slic_init(void) |
| { |
| return platform_driver_register(&nz3_slic_driver); |
| } |
| |
| static void nz3_slic_exit(void) |
| { |
| platform_driver_unregister(&nz3_slic_driver); |
| } |
| module_init(nz3_slic_init); |
| module_exit(nz3_slic_exit); |
| |
| MODULE_AUTHOR("Yu Zhang <zhangy@marvell.com>"); |
| MODULE_DESCRIPTION("driver for Marvell PXA1826 SLIC solution"); |
| MODULE_LICENSE("GPL v2"); |