From: Jang JeongHoon Date: Fri, 11 May 2018 06:48:47 +0000 (+0900) Subject: [COMMON] soc: samsung: Added initial exynos-rgt driver. X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=90c6530bcbc01d2be21dea5263982b9493a9c61e;p=GitHub%2FLineageOS%2Fandroid_kernel_motorola_exynos9610.git [COMMON] soc: samsung: Added initial exynos-rgt driver. Change-Id: Id520def8ec546c9b371149108311c451ed44eb5b Signed-off-by: Jang JeongHoon --- diff --git a/drivers/soc/samsung/Kconfig b/drivers/soc/samsung/Kconfig index f93932ab08d5..6485b81bbe0a 100644 --- a/drivers/soc/samsung/Kconfig +++ b/drivers/soc/samsung/Kconfig @@ -61,6 +61,14 @@ config ECT_DUMP default n depends on ECT +config EXYNOS_RGT + bool "Exynos Regulator Debug Support" + depends on REGULATOR + depends on DEBUG_FS + depends on OF + help + dummy device driver for debugging samsung PMIC + # There is no need to enable these drivers for ARMv8 config EXYNOS_PM_DOMAINS bool "Exynos PM domains" if COMPILE_TEST diff --git a/drivers/soc/samsung/Makefile b/drivers/soc/samsung/Makefile index 4241d68c987c..fb731051dd4f 100644 --- a/drivers/soc/samsung/Makefile +++ b/drivers/soc/samsung/Makefile @@ -19,3 +19,5 @@ obj-$(CONFIG_EXYNOS_CHIPID) += exynos-chipid.o # ACPM obj-$(CONFIG_EXYNOS_ACPM) += acpm/ + +obj-$(CONFIG_EXYNOS_RGT) += exynos-rgt.o diff --git a/drivers/soc/samsung/exynos-rgt.c b/drivers/soc/samsung/exynos-rgt.c new file mode 100644 index 000000000000..6d6367c56406 --- /dev/null +++ b/drivers/soc/samsung/exynos-rgt.c @@ -0,0 +1,396 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; + +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, +}; + +static int exynos_rgt_probe(struct platform_device *pdev) +{ + int ret; + struct exynos_rgt_info *rgt_info; + struct device_node *regulators_np, *reg_np, *pmic_np; + int rgt_idx = 0, ipc_ch; + const char *rgt_name; + struct regulator_ss_info *rgt_ss; + struct regulator_desc rdesc; + + 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; + } + + regulators_np = of_find_node_by_name(NULL, "regulators"); + while (regulators_np) { + 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); + + pmic_np = of_get_parent(regulators_np); + ret = of_property_read_u32(pmic_np, "acpm-ipc-channel", &ipc_ch); + if (!ret) { + rgt_ss = get_regulator_ss(rgt_idx); + 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; + } + } + rgt_idx++; + } + regulators_np = of_find_node_by_name(regulators_np, "regulators"); + } + + 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);