summaryrefslogtreecommitdiff
path: root/drivers/regulator/pf1550-regulator-rpmsg.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/regulator/pf1550-regulator-rpmsg.c')
-rw-r--r--drivers/regulator/pf1550-regulator-rpmsg.c512
1 files changed, 512 insertions, 0 deletions
diff --git a/drivers/regulator/pf1550-regulator-rpmsg.c b/drivers/regulator/pf1550-regulator-rpmsg.c
new file mode 100644
index 000000000000..11a893373390
--- /dev/null
+++ b/drivers/regulator/pf1550-regulator-rpmsg.c
@@ -0,0 +1,512 @@
+/*
+ * pf1550.c - regulator driver for the PF1550
+ *
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright (C) 2017 NXP.
+ * Robin Gong <yibin.gong@freescale.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/err.h>
+#include <linux/slab.h>
+#include <linux/imx_rpmsg.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_qos.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+#include <linux/rpmsg.h>
+#include <linux/uaccess.h>
+#include <linux/virtio.h>
+
+#define PF1550_DBGFS
+#define PF1550_MAX_REGULATOR 7
+#define RPMSG_TIMEOUT 1000
+
+enum pf1550_regs {
+ PF1550_SW1,
+ PF1550_SW2,
+ PF1550_SW3,
+ PF1550_VREFDDR,
+ PF1550_LDO1,
+ PF1550_LDO2,
+ PF1550_LDO3,
+};
+
+enum pf1550_rpmsg_cmd {
+ PF1550_ENABLE,
+ PF1550_DISABLE,
+ PF1550_IS_ENABLED,
+ PF1550_SET_VOL,
+ PF1550_GET_VOL,
+ PF1550_GET_REG,
+ PF1550_SET_REG,
+};
+
+enum pf1550_resp {
+ PF1550_SUCCESS,
+ PF1550_FAILED,
+ PF1550_UNSURPPORT,
+};
+
+enum pf1550_status {
+ PF1550_DISABLED,
+ PF1550_ENABLED,
+};
+
+struct pf1550_regulator_info {
+ struct rpmsg_device *rpdev;
+ struct device *dev;
+ struct pf1550_regulator_rpmsg *msg;
+ struct completion cmd_complete;
+ struct pm_qos_request pm_qos_req;
+ struct mutex lock;
+ struct regulator_desc *regulators;
+};
+
+static struct pf1550_regulator_info pf1550_info;
+
+struct pf1550_regulator_rpmsg {
+ /* common head */
+ struct imx_rpmsg_head header;
+ /* pmic structure */
+ union {
+ u8 regulator;
+ u8 reg;
+ };
+ u8 response;
+ u8 status;
+ union {
+ u32 voltage; /* uV */
+ u32 val;
+ };
+} __attribute__ ((packed));
+
+static int pf1550_send_message(struct pf1550_regulator_rpmsg *msg,
+ struct pf1550_regulator_info *info)
+{
+ int err;
+
+ if (!info->rpdev) {
+ dev_dbg(info->dev,
+ "rpmsg channel not ready, m4 image ready?\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&info->lock);
+ pm_qos_add_request(&info->pm_qos_req, PM_QOS_CPU_DMA_LATENCY, 0);
+
+ msg->header.cate = IMX_RPMSG_PMIC;
+ msg->header.major = IMX_RMPSG_MAJOR;
+ msg->header.minor = IMX_RMPSG_MINOR;
+ msg->header.type = 0;
+
+ /* wait response from rpmsg */
+ reinit_completion(&info->cmd_complete);
+
+ err = rpmsg_send(info->rpdev->ept, (void *)msg,
+ sizeof(struct pf1550_regulator_rpmsg));
+ if (err) {
+ dev_err(&info->rpdev->dev, "rpmsg_send failed: %d\n", err);
+ goto err_out;
+ }
+ err = wait_for_completion_timeout(&info->cmd_complete,
+ msecs_to_jiffies(RPMSG_TIMEOUT));
+ if (!err) {
+ dev_err(&info->rpdev->dev, "rpmsg_send timeout!\n");
+ err = -ETIMEDOUT;
+ goto err_out;
+ }
+
+ err = 0;
+
+err_out:
+ pm_qos_remove_request(&info->pm_qos_req);
+ mutex_unlock(&info->lock);
+
+ dev_dbg(&info->rpdev->dev, "cmd:%d, reg:%d, resp:%d.\n",
+ msg->header.cmd, msg->regulator, msg->response);
+
+ return err;
+}
+
+static int pf1550_enable(struct regulator_dev *reg)
+{
+ struct pf1550_regulator_info *info = rdev_get_drvdata(reg);
+ struct pf1550_regulator_rpmsg msg;
+
+ msg.header.cmd = PF1550_ENABLE;
+ msg.regulator = reg->desc->id;
+
+ return pf1550_send_message(&msg, info);
+}
+
+static int pf1550_disable(struct regulator_dev *reg)
+{
+ struct pf1550_regulator_info *info = rdev_get_drvdata(reg);
+ struct pf1550_regulator_rpmsg msg;
+
+ msg.header.cmd = PF1550_DISABLE;
+ msg.regulator = reg->desc->id;
+
+ return pf1550_send_message(&msg, info);
+}
+
+static int pf1550_is_enabled(struct regulator_dev *reg)
+{
+ struct pf1550_regulator_info *info = rdev_get_drvdata(reg);
+ struct pf1550_regulator_rpmsg msg;
+ int err;
+
+ msg.header.cmd = PF1550_IS_ENABLED;
+ msg.regulator = reg->desc->id;
+
+ err = pf1550_send_message(&msg, info);
+ if (err)
+ return err;
+ /* Here SUCCESS means ENABLED */
+ if (info->msg->status == PF1550_ENABLED)
+ return 1;
+ else
+ return 0;
+}
+
+static int pf1550_set_voltage(struct regulator_dev *reg,
+ int minuV, int uV, unsigned *selector)
+{
+ struct pf1550_regulator_info *info = rdev_get_drvdata(reg);
+ struct pf1550_regulator_rpmsg msg;
+ int err;
+
+ msg.header.cmd = PF1550_SET_VOL;
+ msg.regulator = reg->desc->id;
+ msg.voltage = minuV;
+
+ err = pf1550_send_message(&msg, info);
+ if (err)
+ return err;
+
+ if (info->msg->response == PF1550_UNSURPPORT) {
+ dev_err(info->dev, "Voltages not allowed to set to %d!\n", uV);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int pf1550_get_voltage(struct regulator_dev *reg)
+{
+ struct pf1550_regulator_info *info = rdev_get_drvdata(reg);
+ struct pf1550_regulator_rpmsg msg;
+ int err;
+
+ msg.header.cmd = PF1550_GET_VOL;
+ msg.regulator = reg->desc->id;
+ msg.voltage = 0;
+
+ err = pf1550_send_message(&msg, info);
+ if (err)
+ return err;
+
+ return info->msg->voltage;
+}
+
+/* return the fix voltage */
+static int pf1550_get_fix_voltage(struct regulator_dev *dev)
+{
+ return dev->desc->fixed_uV;
+}
+
+static struct regulator_ops pf1550_sw_ops = {
+ .set_voltage = pf1550_set_voltage,
+ .get_voltage = pf1550_get_voltage,
+};
+
+static struct regulator_ops pf1550_ldo_ops = {
+ .enable = pf1550_enable,
+ .disable = pf1550_disable,
+ .is_enabled = pf1550_is_enabled,
+ .set_voltage = pf1550_set_voltage,
+ .get_voltage = pf1550_get_voltage,
+};
+
+static struct regulator_ops pf1550_fixed_ops = {
+ .enable = pf1550_enable,
+ .disable = pf1550_disable,
+ .get_voltage = pf1550_get_fix_voltage,
+ .is_enabled = pf1550_is_enabled,
+};
+
+static struct regulator_desc pf1550_regulators[PF1550_MAX_REGULATOR] = {
+{
+ .name = "SW1",
+ .of_match = of_match_ptr("SW1"),
+ .id = PF1550_SW1,
+ .ops = &pf1550_sw_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+},
+{
+ .name = "SW2",
+ .of_match = of_match_ptr("SW2"),
+ .id = PF1550_SW2,
+ .ops = &pf1550_sw_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+},
+{
+ .name = "SW3",
+ .of_match = of_match_ptr("SW3"),
+ .id = PF1550_SW3,
+ .ops = &pf1550_sw_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+},
+{
+ .name = "VREFDDR",
+ .of_match = of_match_ptr("VREFDDR"),
+ .id = PF1550_VREFDDR,
+ .ops = &pf1550_fixed_ops,
+ .type = REGULATOR_VOLTAGE,
+ .fixed_uV = 1200000,
+ .owner = THIS_MODULE,
+},
+{
+ .name = "LDO1",
+ .of_match = of_match_ptr("LDO1"),
+ .id = PF1550_LDO1,
+ .ops = &pf1550_ldo_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+},
+{
+ .name = "LDO2",
+ .of_match = of_match_ptr("LDO2"),
+ .id = PF1550_LDO2,
+ .ops = &pf1550_ldo_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+},
+{
+ .name = "LDO3",
+ .of_match = of_match_ptr("LDO3"),
+ .id = PF1550_LDO3,
+ .ops = &pf1550_ldo_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+},
+};
+
+
+static int rpmsg_regulator_cb(struct rpmsg_device *rpdev, void *data, int len,
+ void *priv, u32 src)
+{
+ struct pf1550_regulator_rpmsg *msg = (struct pf1550_regulator_rpmsg *)data;
+
+
+ dev_dbg(&rpdev->dev, "get from%d: cmd:%d, reg:%d, resp:%d.\n",
+ src, msg->header.cmd, msg->regulator, msg->response);
+
+ pf1550_info.msg = msg;
+
+ complete(&pf1550_info.cmd_complete);
+
+ return 0;
+}
+
+static int rpmsg_regulator_probe(struct rpmsg_device *rpdev)
+{
+ pf1550_info.rpdev = rpdev;
+
+ init_completion(&pf1550_info.cmd_complete);
+ mutex_init(&pf1550_info.lock);
+
+ dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
+ rpdev->src, rpdev->dst);
+ return 0;
+}
+
+static void rpmsg_regulator_remove(struct rpmsg_device *rpdev)
+{
+ dev_info(&rpdev->dev, "rpmsg regulator driver is removed\n");
+}
+
+static struct rpmsg_device_id rpmsg_regulator_id_table[] = {
+ { .name = "rpmsg-regulator-channel" },
+ { },
+};
+
+static struct rpmsg_driver rpmsg_regulator_driver = {
+ .drv.name = "regulator_rpmsg",
+ .drv.owner = THIS_MODULE,
+ .id_table = rpmsg_regulator_id_table,
+ .probe = rpmsg_regulator_probe,
+ .callback = rpmsg_regulator_cb,
+ .remove = rpmsg_regulator_remove,
+};
+
+#ifdef PF1550_DBGFS
+#define MAX_REGS 0xff
+
+/*
+ * Alligned the below two functions as the same as regmap_map_read_file
+ * and regmap_map_write_file in regmap-debugfs.c
+ */
+static ssize_t pf1550_registers_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct pf1550_regulator_info *info = platform_get_drvdata(pdev);
+ struct pf1550_regulator_rpmsg msg;
+ int err;
+ size_t bufpos = 0, count = MAX_REGS * 7;
+
+ for (i = 0; i < MAX_REGS; i++) {
+ snprintf(buf + bufpos, count - bufpos, "%.*x: ", 2, i);
+ bufpos += 4;
+
+ msg.header.cmd = PF1550_GET_REG;
+ msg.reg = i;
+ msg.val = 0;
+
+ err = pf1550_send_message(&msg, info);
+ if (err)
+ return err;
+
+ if (info->msg->response != PF1550_SUCCESS) {
+ dev_err(info->dev, "Get register failed %x, resp=%x!\n",
+ i, info->msg->response);
+ return -EINVAL;
+ }
+
+ snprintf(buf + bufpos, count - bufpos, "%.*x\n", 2,
+ info->msg->val);
+ bufpos += 2;
+
+ buf[bufpos++] = '\n';
+ }
+
+ return bufpos;
+}
+
+static ssize_t pf1550_register_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct pf1550_regulator_info *info = platform_get_drvdata(pdev);
+ struct pf1550_regulator_rpmsg msg;
+ char *start = (char *)buf;
+ unsigned long reg, value;
+ int err;
+
+ while (*start == ' ')
+ start++;
+ reg = simple_strtoul(start, &start, 16);
+
+ while (*start == ' ')
+ start++;
+ if (kstrtoul(start, 16, &value))
+ return -EINVAL;
+
+ msg.header.cmd = PF1550_SET_REG;
+ msg.reg = reg;
+ msg.val = value;
+
+ err = pf1550_send_message(&msg, info);
+ if (err)
+ return err;
+
+ if (info->msg->response != PF1550_SUCCESS) {
+ dev_err(info->dev, "set register failed %lx!\n", reg);
+ return -EINVAL;
+ }
+
+ return size;
+}
+
+static DEVICE_ATTR(regs, 0644, pf1550_registers_show, pf1550_register_store);
+#endif
+
+static int pf1550_regulator_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ int i;
+ struct regulator_config config = { };
+
+ if (!np)
+ return -ENODEV;
+
+ config.dev = &pdev->dev;
+ config.driver_data = &pf1550_info;
+ pf1550_info.dev = &pdev->dev;
+ pf1550_info.regulators = pf1550_regulators;
+
+ for (i = 0; i < ARRAY_SIZE(pf1550_regulators); i++) {
+ struct regulator_dev *rdev;
+ struct regulator_desc *desc;
+
+ desc = &pf1550_info.regulators[i];
+ rdev = devm_regulator_register(&pdev->dev, desc, &config);
+ if (IS_ERR(rdev)) {
+ dev_err(&pdev->dev,
+ "Failed to initialize regulator-%d\n", i);
+ return PTR_ERR(rdev);
+ }
+ }
+
+#ifdef PF1550_DBGFS
+ i = sysfs_create_file(&config.dev->kobj, &dev_attr_regs.attr);
+ if (i) {
+ dev_err(&pdev->dev, "Failed to create pf1550 debug sysfs.\n");
+ return i;
+ }
+#endif
+
+ platform_set_drvdata(pdev, &pf1550_info);
+
+ return 0;
+}
+
+static const struct of_device_id pf1550_regulator_id[] = {
+ {"fsl,pf1550-rpmsg",},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, pf1550_regulator_id);
+
+static struct platform_driver pf1550_regulator_driver = {
+ .driver = {
+ .name = "pf1550-rpmsg",
+ .owner = THIS_MODULE,
+ .of_match_table = pf1550_regulator_id,
+ },
+ .probe = pf1550_regulator_probe,
+};
+
+static int __init pf1550_rpmsg_init(void)
+{
+ return register_rpmsg_driver(&rpmsg_regulator_driver);
+}
+
+module_platform_driver(pf1550_regulator_driver);
+module_init(pf1550_rpmsg_init);
+
+MODULE_DESCRIPTION("Freescale PF1550 regulator rpmsg driver");
+MODULE_AUTHOR("Robin Gong <yibin.gong@freescale.com>");
+MODULE_LICENSE("GPL");