| /* |
| * Exynos regulator support. |
| * |
| * Copyright (c) 2016 Samsung Electronics Co., Ltd. |
| * http://www.samsung.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. |
| */ |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/of_address.h> |
| #include <linux/of_platform.h> |
| #include <linux/sched.h> |
| #include <linux/fs.h> |
| #include <linux/uaccess.h> |
| #include <linux/mutex.h> |
| #include <linux/regulator/of_regulator.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/regulator/driver.h> |
| #include <linux/regulator/machine.h> |
| #include <linux/debugfs.h> |
| #include <linux/device.h> |
| #include <linux/slab.h> |
| #include "../../regulator/internal.h" |
| #include <../drivers/soc/samsung/acpm/acpm_ipc.h> |
| |
| #define EXYNOS_RGT_PREFIX "EXYNOS-RGT: " |
| |
| struct exynos_rgt_info { |
| struct regulator *rgt; |
| struct dentry *reg_dir; |
| struct dentry *f_get; |
| struct dentry *f_ena; |
| struct dentry *f_volt; |
| struct file_operations get_fops; |
| struct file_operations ena_fops; |
| struct file_operations volt_fops; |
| }; |
| |
| static struct dentry *exynos_rgt_root; |
| static int num_regulators = 0; |
| |
| #ifdef CONFIG_DEBUG_SNAPSHOT_REGULATOR |
| #define REG_MAP_MAX (0x100) |
| |
| struct regulator_ss_info { |
| char name[7]; |
| unsigned int min_uV; |
| unsigned int uV_step; |
| unsigned int linear_min_sel; |
| unsigned int vsel_reg; |
| }; |
| |
| struct exynos_rgt_ss_info { |
| u32 acpm_pmic_cnt; |
| u32 *acpm_reg_cnt; |
| struct regulator_ss_info **regulator_ss; |
| char **reg_map; |
| bool is_reg_map_set; |
| }; |
| struct exynos_rgt_ss_info rgt_ss_info; |
| #endif |
| |
| static const char *rdev_get_name(struct regulator_dev *rdev) |
| { |
| if (rdev->desc->name) |
| return rdev->desc->name; |
| else if (rdev->constraints && rdev->constraints->name) |
| return rdev->constraints->name; |
| else |
| return ""; |
| } |
| |
| static ssize_t exynos_rgt_get_read(struct file *file, char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| struct regulator *cons, *rgt = file->private_data; |
| const char *dev; |
| char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL); |
| ssize_t len, ret = 0; |
| |
| if (!buf) |
| return -ENOMEM; |
| |
| len = snprintf(buf + ret, PAGE_SIZE - ret, "[%s]\t open_count %d\n", |
| rdev_get_name(rgt->rdev), |
| rgt->rdev->open_count); |
| if (len > 0) |
| ret += len; |
| |
| len = snprintf(buf + ret, PAGE_SIZE - ret, "consumer list ->\n"); |
| if (len > 0) |
| ret += len; |
| |
| if (list_empty(&rgt->rdev->consumer_list)) |
| goto skip; |
| |
| list_for_each_entry(cons, &rgt->rdev->consumer_list, list) { |
| if (cons && cons->dev && dev_name(cons->dev)) |
| dev = dev_name(cons->dev); |
| else |
| dev = "unknown"; |
| |
| len = snprintf(buf + ret, PAGE_SIZE - ret, "\t [%s]\n", dev); |
| if (len > 0) |
| ret += len; |
| |
| if (ret > PAGE_SIZE) { |
| ret = PAGE_SIZE; |
| break; |
| } |
| } |
| skip: |
| ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); |
| |
| kfree(buf); |
| return ret; |
| } |
| |
| static ssize_t exynos_rgt_ena_read(struct file *file, char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| struct regulator *rgt = file->private_data; |
| char buf[80]; |
| ssize_t ret; |
| |
| if (!rgt->rdev->desc->ops->is_enabled) { |
| pr_err("There is no is_enabled callback on this regulator\n"); |
| return -ENODEV; |
| } |
| |
| ret = snprintf(buf, sizeof(buf), "[%s]\t %s (always_on %d, use_count %d)\n", |
| rdev_get_name(rgt->rdev), |
| rgt->rdev->desc->ops->is_enabled(rgt->rdev) ? "enabled " : "disabled", |
| rgt->rdev->constraints->always_on, |
| rgt->rdev->use_count); |
| if (ret < 0) |
| return ret; |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buf, ret); |
| } |
| |
| static ssize_t exynos_rgt_ena_write(struct file *file, const char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| struct regulator *rgt = file->private_data; |
| char buf[32]; |
| ssize_t len, ret; |
| |
| len = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); |
| if (len < 0) |
| return len; |
| |
| buf[len] = '\0'; |
| |
| switch (buf[0]) { |
| case '0': |
| ret = regulator_disable(rgt); |
| if (ret) |
| return ret; |
| break; |
| case '1': |
| ret = regulator_enable(rgt); |
| if (ret) |
| return ret; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return len; |
| } |
| |
| static ssize_t exynos_rgt_volt_read(struct file *file, char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| struct regulator *cons, *rgt = file->private_data; |
| struct regulation_constraints *constraints = rgt->rdev->constraints; |
| const char *dev; |
| char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL); |
| ssize_t len, ret = 0; |
| |
| if (!buf) |
| return -ENOMEM; |
| |
| len = snprintf(buf + ret, PAGE_SIZE - ret, "[%s]\t curr %4d mV\t constraint min %4d mV, max %4d mV\n", |
| rdev_get_name(rgt->rdev), |
| regulator_get_voltage(rgt) / 1000, |
| constraints->min_uV / 1000, |
| constraints->max_uV / 1000); |
| if (len > 0) |
| ret += len; |
| |
| len = snprintf(buf + ret, PAGE_SIZE - ret, "consumer list ->\n"); |
| if (len > 0) |
| ret += len; |
| |
| if (list_empty(&rgt->rdev->consumer_list)) |
| goto skip; |
| |
| list_for_each_entry(cons, &rgt->rdev->consumer_list, list) { |
| if (cons && cons->dev && dev_name(cons->dev)) |
| dev = dev_name(cons->dev); |
| else |
| dev = "unknown"; |
| |
| len = snprintf(buf + ret, PAGE_SIZE - ret, |
| "\t [%s]\t min %4d mV, max %4d mV %s\n", |
| dev, |
| cons->min_uV / 1000, |
| cons->max_uV / 1000, |
| cons->min_uV ? "(requested)" : ""); |
| if (len > 0) |
| ret += len; |
| |
| if (ret > PAGE_SIZE) { |
| ret = PAGE_SIZE; |
| break; |
| } |
| } |
| skip: |
| ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); |
| |
| kfree(buf); |
| return ret; |
| } |
| |
| static ssize_t exynos_rgt_volt_write(struct file *file, const char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| struct regulator *rgt = file->private_data; |
| int min_mV, min_uV, max_uV = rgt->rdev->constraints->max_uV; |
| char buf[32]; |
| ssize_t len, ret; |
| |
| len = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); |
| if (len < 0) |
| return len; |
| |
| buf[len] = '\0'; |
| |
| ret = kstrtos32(buf, 10, &min_mV); |
| if (ret) |
| return ret; |
| |
| min_uV = min_mV * 1000; |
| |
| if (min_uV < rgt->rdev->constraints->min_uV || min_uV > max_uV) |
| return -EINVAL; |
| |
| ret = regulator_set_voltage(rgt, min_uV, max_uV); |
| if (ret) |
| return ret; |
| |
| return len; |
| } |
| |
| static const struct file_operations exynos_rgt_get_fops = { |
| .open = simple_open, |
| .read = exynos_rgt_get_read, |
| .llseek = default_llseek, |
| }; |
| |
| static const struct file_operations exynos_rgt_ena_fops = { |
| .open = simple_open, |
| .read = exynos_rgt_ena_read, |
| .write = exynos_rgt_ena_write, |
| .llseek = default_llseek, |
| }; |
| |
| static const struct file_operations exynos_rgt_volt_fops = { |
| .open = simple_open, |
| .read = exynos_rgt_volt_read, |
| .write = exynos_rgt_volt_write, |
| .llseek = default_llseek, |
| }; |
| |
| #ifdef CONFIG_DEBUG_SNAPSHOT_REGULATOR |
| struct regulator_ss_info *get_regulator_ss(u32 rgt_idx, u32 spd_ch) |
| { |
| if (spd_ch < rgt_ss_info.acpm_pmic_cnt && rgt_idx < rgt_ss_info.acpm_reg_cnt[spd_ch]) |
| return &(rgt_ss_info.regulator_ss[spd_ch][rgt_idx]); |
| else |
| return NULL; |
| } |
| |
| static void set_reg_map(void) |
| { |
| u32 idx; |
| int i, j; |
| |
| for (i = 0; i < rgt_ss_info.acpm_pmic_cnt; i++) { |
| for (j = 0; j < rgt_ss_info.acpm_reg_cnt[i]; j++) { |
| idx = rgt_ss_info.regulator_ss[i][j].vsel_reg & 0xFF; |
| if (idx == 0) |
| continue; |
| |
| if ((rgt_ss_info.reg_map[i][idx]) != 0) |
| pr_err("duplicated set_reg_map [%d] reg_map %x\n", j, |
| rgt_ss_info.reg_map[i][idx]); |
| |
| rgt_ss_info.reg_map[i][idx] = j; |
| } |
| } |
| |
| rgt_ss_info.is_reg_map_set = true; |
| } |
| |
| static u32 get_reg_id(u32 ch, u32 addr) |
| { |
| u32 id; |
| |
| if (rgt_ss_info.is_reg_map_set == false) |
| return REG_MAP_MAX; |
| |
| if (addr >> 8 != 0x1 || ch >= rgt_ss_info.acpm_pmic_cnt) |
| return REG_MAP_MAX; |
| |
| id = rgt_ss_info.reg_map[ch][addr & 0xff]; |
| if (id != 0) |
| return id; |
| |
| return REG_MAP_MAX; |
| } |
| |
| static u32 get_reg_voltage(struct regulator_ss_info reg_info, u32 selector) |
| { |
| if (reg_info.name[0] == 'L') |
| selector = selector & 0x3F; |
| |
| return reg_info.min_uV + (reg_info.uV_step * (selector - reg_info.linear_min_sel)); |
| } |
| |
| static int prepare_dss_regulator(struct platform_device *pdev) |
| { |
| struct device_node *exynos_rgt_np = pdev->dev.of_node; |
| struct device_node *regulators_np, *pmic_np; |
| int ret, ipc_ch, spd_ch, i; |
| |
| ret = of_property_read_u32(exynos_rgt_np, "num-acpm-pmic", &(rgt_ss_info.acpm_pmic_cnt)); |
| if (ret) { |
| pr_err("%s parse fail on num-acpm-pmic\n", EXYNOS_RGT_PREFIX); |
| return -ENODEV; |
| } |
| |
| rgt_ss_info.acpm_reg_cnt = kzalloc(sizeof(u32) * rgt_ss_info.acpm_pmic_cnt, GFP_KERNEL); |
| if (!rgt_ss_info.acpm_reg_cnt) { |
| pr_err("%s mem alloc fail on acpm_reg_cnt.\n", EXYNOS_RGT_PREFIX); |
| return -ENOMEM; |
| } |
| |
| regulators_np = of_find_node_by_name(NULL, "regulators"); |
| while (regulators_np) { |
| pmic_np = of_get_parent(regulators_np); |
| ret = of_property_read_u32(pmic_np, "acpm-ipc-channel", &ipc_ch); |
| if (!ret) { |
| ret = of_property_read_u32(pmic_np, "acpm-speedy-channel", &spd_ch); |
| if (!ret) { |
| if (spd_ch < rgt_ss_info.acpm_pmic_cnt) { |
| rgt_ss_info.acpm_reg_cnt[spd_ch] = of_get_child_count(regulators_np); |
| } |
| } |
| } |
| regulators_np = of_find_node_by_name(regulators_np, "regulators"); |
| } |
| |
| rgt_ss_info.regulator_ss = kzalloc(sizeof(struct regulator_ss_info *) * rgt_ss_info.acpm_pmic_cnt, GFP_KERNEL); |
| if (!(rgt_ss_info.regulator_ss)) { |
| pr_err("%s mem alloc fail on *regulator_ss.\n", EXYNOS_RGT_PREFIX); |
| return -ENOMEM; |
| } |
| for (i = 0; i < rgt_ss_info.acpm_pmic_cnt; i++) { |
| rgt_ss_info.regulator_ss[i] = kzalloc(sizeof(struct regulator_ss_info) * rgt_ss_info.acpm_reg_cnt[i], GFP_KERNEL); |
| if (!(rgt_ss_info.regulator_ss[i])) { |
| pr_err("%s mem alloc fail on regulator_ss.\n", EXYNOS_RGT_PREFIX); |
| return -ENOMEM; |
| } |
| } |
| |
| rgt_ss_info.reg_map = kzalloc(sizeof(char *) * rgt_ss_info.acpm_pmic_cnt, GFP_KERNEL); |
| if (!(rgt_ss_info.reg_map)) { |
| pr_err("%s mem alloc fail on *reg_map.\n", EXYNOS_RGT_PREFIX); |
| return -ENOMEM; |
| } |
| for (i = 0; i < rgt_ss_info.acpm_pmic_cnt; i++) { |
| rgt_ss_info.reg_map[i] = kzalloc(sizeof(char) * REG_MAP_MAX, GFP_KERNEL); |
| if (!(rgt_ss_info.reg_map[i])) { |
| pr_err("%s mem alloc fail on reg_map.\n", EXYNOS_RGT_PREFIX); |
| return -ENOMEM; |
| } |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| void exynos_rgt_dbg_snapshot_regulator(u32 val, unsigned long long time) |
| { |
| #ifdef CONFIG_DEBUG_SNAPSHOT_REGULATOR |
| u32 spd_ch, reg_addr, mod_addr, reg_data, reg_id; |
| |
| spd_ch = (val >> 28) & 0xf; |
| reg_addr = (val >> 12) & 0xfff; |
| reg_data = (val >> 4) & 0xff; |
| mod_addr = (spd_ch << 12) | reg_addr; |
| reg_id = get_reg_id(spd_ch, reg_addr); |
| |
| if (reg_id == REG_MAP_MAX) |
| dbg_snapshot_regulator(time, "CHKADDR", mod_addr, reg_data, reg_data, val & 0xF); |
| else |
| dbg_snapshot_regulator(time, rgt_ss_info.regulator_ss[spd_ch][reg_id].name, mod_addr, |
| get_reg_voltage(rgt_ss_info.regulator_ss[spd_ch][reg_id], reg_data), |
| reg_data, val & 0xf); |
| #endif |
| return ; |
| } |
| |
| static int exynos_rgt_probe(struct platform_device *pdev) |
| { |
| int ret; |
| struct exynos_rgt_info *rgt_info; |
| struct device_node *regulators_np, *reg_np; |
| int rgt_idx = 0; |
| const char *rgt_name; |
| #ifdef CONFIG_DEBUG_SNAPSHOT_REGULATOR |
| struct regulator_desc rdesc; |
| int rgt_ss_idx, ipc_ch, spd_ch; |
| struct device_node *pmic_np; |
| struct regulator_ss_info *rgt_ss; |
| #endif |
| |
| regulators_np = of_find_node_by_name(NULL, "regulators"); |
| if (!regulators_np) { |
| pr_err("%s %s: could not find regulators sub-node\n", EXYNOS_RGT_PREFIX, __func__); |
| ret = -EINVAL; |
| goto err_find_regs; |
| } |
| |
| while (regulators_np) { |
| num_regulators += of_get_child_count(regulators_np); |
| regulators_np = of_find_node_by_name(regulators_np, "regulators"); |
| } |
| |
| rgt_info = kzalloc(sizeof(struct exynos_rgt_info) * num_regulators, GFP_KERNEL); |
| if (!rgt_info) { |
| pr_err("%s %s: could not allocate mem for rgt_info\n", EXYNOS_RGT_PREFIX, __func__); |
| ret = -ENOMEM; |
| goto err_rgt_info; |
| } |
| |
| exynos_rgt_root = debugfs_create_dir("exynos-rgt", NULL); |
| if (!exynos_rgt_root) { |
| pr_err("%s %s: could not create debugfs root dir\n", |
| EXYNOS_RGT_PREFIX, __func__); |
| ret = -ENOMEM; |
| goto err_dbgfs_root; |
| } |
| |
| #ifdef CONFIG_DEBUG_SNAPSHOT_REGULATOR |
| ret = prepare_dss_regulator(pdev); |
| if (ret) |
| goto err_dbgfs_root; |
| #endif |
| regulators_np = of_find_node_by_name(NULL, "regulators"); |
| while (regulators_np) { |
| #ifdef CONFIG_DEBUG_SNAPSHOT_REGULATOR |
| rgt_ss_idx = 0; |
| #endif |
| for_each_child_of_node(regulators_np, reg_np) { |
| rgt_name = of_get_property(reg_np, "regulator-name", NULL); |
| if (!rgt_name) |
| continue; |
| |
| rgt_info[rgt_idx].rgt = regulator_get(&pdev->dev, rgt_name); |
| if (IS_ERR(rgt_info[rgt_idx].rgt)) { |
| pr_err("%s %s: failed to getting regulator %s\n", EXYNOS_RGT_PREFIX, __func__, rgt_name); |
| continue; |
| } |
| |
| rgt_info[rgt_idx].get_fops = exynos_rgt_get_fops; |
| rgt_info[rgt_idx].ena_fops = exynos_rgt_ena_fops; |
| rgt_info[rgt_idx].volt_fops = exynos_rgt_volt_fops; |
| rgt_info[rgt_idx].reg_dir = |
| debugfs_create_dir(rgt_name, exynos_rgt_root); |
| rgt_info[rgt_idx].f_get = |
| debugfs_create_file("get", 0400, rgt_info[rgt_idx].reg_dir, |
| rgt_info[rgt_idx].rgt, &rgt_info[rgt_idx].get_fops); |
| rgt_info[rgt_idx].f_ena = |
| debugfs_create_file("enable", 0600, rgt_info[rgt_idx].reg_dir, |
| rgt_info[rgt_idx].rgt, &rgt_info[rgt_idx].ena_fops); |
| rgt_info[rgt_idx].f_volt = |
| debugfs_create_file("voltage", 0600, rgt_info[rgt_idx].reg_dir, |
| rgt_info[rgt_idx].rgt, &rgt_info[rgt_idx].volt_fops); |
| #ifdef CONFIG_DEBUG_SNAPSHOT_REGULATOR |
| pmic_np = of_get_parent(regulators_np); |
| ret = of_property_read_u32(pmic_np, "acpm-ipc-channel", &ipc_ch); |
| if (!ret) { |
| ret = of_property_read_u32(pmic_np, "acpm-speedy-channel", &spd_ch); |
| if (!ret) { |
| rgt_ss = get_regulator_ss(rgt_ss_idx++, spd_ch); |
| if (rgt_ss != NULL) { |
| rdesc = *(rgt_info[rgt_idx].rgt->rdev->desc); |
| |
| snprintf(rgt_ss->name, sizeof(rgt_ss->name), "%s.", rdesc.name); |
| rgt_ss->min_uV = rdesc.min_uV; |
| rgt_ss->uV_step = rdesc.uV_step; |
| rgt_ss->linear_min_sel = rdesc.linear_min_sel; |
| rgt_ss->vsel_reg = rdesc.vsel_reg; |
| } |
| } |
| } |
| #endif |
| rgt_idx++; |
| } |
| regulators_np = of_find_node_by_name(regulators_np, "regulators"); |
| } |
| #ifdef CONFIG_DEBUG_SNAPSHOT_REGULATOR |
| set_reg_map(); |
| #endif |
| platform_set_drvdata(pdev, rgt_info); |
| |
| return 0; |
| |
| err_dbgfs_root: |
| kfree(rgt_info); |
| err_rgt_info: |
| err_find_regs: |
| return ret; |
| } |
| |
| static int exynos_rgt_remove(struct platform_device *pdev) |
| { |
| struct exynos_rgt_info *rgt_info = platform_get_drvdata(pdev); |
| int i = 0; |
| |
| for (i = 0; i < num_regulators; i++) { |
| debugfs_remove_recursive(rgt_info[i].f_volt); |
| debugfs_remove_recursive(rgt_info[i].f_ena); |
| debugfs_remove_recursive(rgt_info[i].f_get); |
| debugfs_remove_recursive(rgt_info[i].reg_dir); |
| regulator_put(rgt_info[i].rgt); |
| } |
| |
| debugfs_remove_recursive(exynos_rgt_root); |
| kfree(rgt_info); |
| |
| platform_set_drvdata(pdev, NULL); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id exynos_rgt_match[] = { |
| { |
| .compatible = "samsung,exynos-rgt", |
| }, |
| {}, |
| }; |
| |
| static struct platform_driver exynos_rgt_drv = { |
| .probe = exynos_rgt_probe, |
| .remove = exynos_rgt_remove, |
| .driver = { |
| .name = "exynos_rgt", |
| .owner = THIS_MODULE, |
| .of_match_table = exynos_rgt_match, |
| }, |
| }; |
| |
| static int __init exynos_rgt_init(void) |
| { |
| return platform_driver_register(&exynos_rgt_drv); |
| } |
| late_initcall(exynos_rgt_init); |