| /* Copyright (c) 2019, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only 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. |
| */ |
| |
| #define pr_fmt(fmt) "%s: " fmt, __func__ |
| |
| #include <linux/spinlock.h> |
| #include <linux/slab.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| #include <linux/virtio.h> |
| #include <linux/virtio_regulator.h> |
| #include <linux/scatterlist.h> |
| #include <linux/delay.h> |
| #include <linux/completion.h> |
| #include <linux/mutex.h> |
| #include <linux/regulator/driver.h> |
| #include <linux/regulator/machine.h> |
| #include <linux/regulator/of_regulator.h> |
| |
| #define VIRTIO_REGULATOR_MAX_NAME 20 |
| #define VIRTIO_REGULATOR_VOLTAGE_UNKNOWN 1 |
| |
| struct reg_virtio; |
| |
| struct virtio_regulator { |
| struct virtio_device *vdev; |
| struct virtqueue *vq; |
| struct completion rsp_avail; |
| struct mutex lock; |
| struct reg_virtio *regs; |
| int regs_count; |
| }; |
| |
| struct reg_virtio { |
| struct device_node *of_node; |
| struct regulator_desc rdesc; |
| struct regulator_dev *rdev; |
| struct virtio_regulator *vreg; |
| char name[VIRTIO_REGULATOR_MAX_NAME]; |
| bool enabled; |
| }; |
| |
| static int virtio_regulator_enable(struct regulator_dev *rdev) |
| { |
| struct reg_virtio *reg = rdev_get_drvdata(rdev); |
| struct virtio_regulator *vreg = reg->vreg; |
| struct virtio_regulator_msg *req, *rsp; |
| struct scatterlist sg[1]; |
| unsigned int len; |
| int ret = 0; |
| |
| req = kzalloc(sizeof(struct virtio_regulator_msg), GFP_KERNEL); |
| if (!req) |
| return -ENOMEM; |
| |
| strlcpy(req->name, reg->rdesc.name, sizeof(req->name)); |
| req->type = cpu_to_virtio32(vreg->vdev, VIRTIO_REGULATOR_T_ENABLE); |
| sg_init_one(sg, req, sizeof(*req)); |
| |
| mutex_lock(&vreg->lock); |
| |
| ret = virtqueue_add_outbuf(vreg->vq, sg, 1, req, GFP_KERNEL); |
| if (ret) { |
| pr_err("%s: fail to add output buffer\n", reg->rdesc.name); |
| goto out; |
| } |
| |
| virtqueue_kick(vreg->vq); |
| |
| wait_for_completion(&vreg->rsp_avail); |
| |
| rsp = virtqueue_get_buf(vreg->vq, &len); |
| if (!rsp) { |
| pr_err("%s: fail to get virtqueue buffer\n", reg->rdesc.name); |
| ret = -EIO; |
| goto out; |
| } |
| |
| ret = virtio32_to_cpu(vreg->vdev, rsp->result); |
| |
| if (!ret) |
| reg->enabled = true; |
| out: |
| mutex_unlock(&vreg->lock); |
| kfree(req); |
| |
| pr_debug("%s return %d\n", reg->rdesc.name, ret); |
| |
| return ret; |
| } |
| |
| static int virtio_regulator_disable(struct regulator_dev *rdev) |
| { |
| struct reg_virtio *reg = rdev_get_drvdata(rdev); |
| struct virtio_regulator *vreg = reg->vreg; |
| struct virtio_regulator_msg *req, *rsp; |
| struct scatterlist sg[1]; |
| unsigned int len; |
| int ret = 0; |
| |
| req = kzalloc(sizeof(struct virtio_regulator_msg), GFP_KERNEL); |
| if (!req) |
| return -ENOMEM; |
| |
| strlcpy(req->name, reg->rdesc.name, sizeof(req->name)); |
| req->type = cpu_to_virtio32(vreg->vdev, VIRTIO_REGULATOR_T_DISABLE); |
| sg_init_one(sg, req, sizeof(*req)); |
| |
| mutex_lock(&vreg->lock); |
| |
| ret = virtqueue_add_outbuf(vreg->vq, sg, 1, req, GFP_KERNEL); |
| if (ret) { |
| pr_err("%s: fail to add output buffer\n", reg->rdesc.name); |
| goto out; |
| } |
| |
| virtqueue_kick(vreg->vq); |
| |
| wait_for_completion(&vreg->rsp_avail); |
| |
| rsp = virtqueue_get_buf(vreg->vq, &len); |
| if (!rsp) { |
| pr_err("%s: fail to get virtqueue buffer\n", reg->rdesc.name); |
| ret = -EIO; |
| goto out; |
| } |
| |
| ret = virtio32_to_cpu(vreg->vdev, rsp->result); |
| |
| if (!ret) |
| reg->enabled = false; |
| out: |
| mutex_unlock(&vreg->lock); |
| kfree(req); |
| |
| pr_debug("%s return %d\n", reg->rdesc.name, ret); |
| |
| return ret; |
| } |
| |
| static int virtio_regulator_is_enabled(struct regulator_dev *rdev) |
| { |
| struct reg_virtio *reg = rdev_get_drvdata(rdev); |
| |
| pr_debug("%s return %d\n", reg->rdesc.name, reg->enabled); |
| |
| return reg->enabled; |
| } |
| |
| static int virtio_regulator_set_voltage(struct regulator_dev *rdev, int min_uV, |
| int max_uV, unsigned int *selector) |
| { |
| struct reg_virtio *reg = rdev_get_drvdata(rdev); |
| struct virtio_regulator *vreg = reg->vreg; |
| struct virtio_regulator_msg *req, *rsp; |
| struct scatterlist sg[1]; |
| unsigned int len; |
| int ret = 0; |
| |
| req = kzalloc(sizeof(struct virtio_regulator_msg), GFP_KERNEL); |
| if (!req) |
| return -ENOMEM; |
| |
| strlcpy(req->name, reg->rdesc.name, sizeof(req->name)); |
| req->type = cpu_to_virtio32(vreg->vdev, VIRTIO_REGULATOR_T_SET_VOLTAGE); |
| req->data[0] = cpu_to_virtio32(vreg->vdev, DIV_ROUND_UP(min_uV, 1000)); |
| req->data[1] = cpu_to_virtio32(vreg->vdev, max_uV / 1000); |
| sg_init_one(sg, req, sizeof(*req)); |
| |
| mutex_lock(&vreg->lock); |
| |
| ret = virtqueue_add_outbuf(vreg->vq, sg, 1, req, GFP_KERNEL); |
| if (ret) { |
| pr_err("%s: fail to add output buffer\n", reg->rdesc.name); |
| goto out; |
| } |
| |
| virtqueue_kick(vreg->vq); |
| |
| wait_for_completion(&vreg->rsp_avail); |
| |
| rsp = virtqueue_get_buf(vreg->vq, &len); |
| if (!rsp) { |
| pr_err("%s: fail to get virtqueue buffer\n", reg->rdesc.name); |
| ret = -EIO; |
| goto out; |
| } |
| |
| ret = virtio32_to_cpu(vreg->vdev, rsp->result); |
| |
| out: |
| mutex_unlock(&vreg->lock); |
| kfree(req); |
| |
| pr_debug("%s return %d\n", reg->rdesc.name, ret); |
| |
| return ret; |
| } |
| |
| static int virtio_regulator_get_voltage(struct regulator_dev *rdev) |
| { |
| struct reg_virtio *reg = rdev_get_drvdata(rdev); |
| struct virtio_regulator *vreg = reg->vreg; |
| struct virtio_regulator_msg *req, *rsp; |
| struct scatterlist sg[1]; |
| unsigned int len; |
| int ret = 0; |
| |
| req = kzalloc(sizeof(struct virtio_regulator_msg), GFP_KERNEL); |
| if (!req) |
| return -ENOMEM; |
| |
| strlcpy(req->name, reg->rdesc.name, sizeof(req->name)); |
| req->type = cpu_to_virtio32(vreg->vdev, VIRTIO_REGULATOR_T_GET_VOLTAGE); |
| sg_init_one(sg, req, sizeof(*req)); |
| |
| mutex_lock(&vreg->lock); |
| |
| ret = virtqueue_add_outbuf(vreg->vq, sg, 1, req, GFP_KERNEL); |
| if (ret) { |
| pr_err("%s: fail to add output buffer\n", reg->rdesc.name); |
| goto out; |
| } |
| |
| virtqueue_kick(vreg->vq); |
| |
| wait_for_completion(&vreg->rsp_avail); |
| |
| rsp = virtqueue_get_buf(vreg->vq, &len); |
| if (!rsp) { |
| pr_err("%s: fail to get virtqueue buffer\n", reg->rdesc.name); |
| ret = -EIO; |
| goto out; |
| } |
| |
| if (rsp->result) { |
| pr_debug("%s: error response (%d)\n", reg->rdesc.name, |
| virtio32_to_cpu(vreg->vdev, rsp->result)); |
| ret = VIRTIO_REGULATOR_VOLTAGE_UNKNOWN; |
| } else |
| ret = virtio32_to_cpu(vreg->vdev, rsp->data[0]) * 1000; |
| |
| out: |
| mutex_unlock(&vreg->lock); |
| kfree(req); |
| |
| pr_debug("%s return %d\n", reg->rdesc.name, ret); |
| |
| return ret; |
| } |
| |
| static int virtio_regulator_set_mode(struct regulator_dev *rdev, |
| unsigned int mode) |
| { |
| struct reg_virtio *reg = rdev_get_drvdata(rdev); |
| struct virtio_regulator *vreg = reg->vreg; |
| struct virtio_regulator_msg *req, *rsp; |
| struct scatterlist sg[1]; |
| unsigned int len; |
| int ret = 0; |
| |
| req = kzalloc(sizeof(struct virtio_regulator_msg), GFP_KERNEL); |
| if (!req) |
| return -ENOMEM; |
| |
| strlcpy(req->name, reg->rdesc.name, sizeof(req->name)); |
| req->type = cpu_to_virtio32(vreg->vdev, VIRTIO_REGULATOR_T_SET_MODE); |
| req->data[0] = cpu_to_virtio32(vreg->vdev, mode); |
| sg_init_one(sg, req, sizeof(*req)); |
| |
| mutex_lock(&vreg->lock); |
| |
| ret = virtqueue_add_outbuf(vreg->vq, sg, 1, req, GFP_KERNEL); |
| if (ret) { |
| pr_err("%s: fail to add output buffer\n", reg->rdesc.name); |
| goto out; |
| } |
| |
| virtqueue_kick(vreg->vq); |
| |
| wait_for_completion(&vreg->rsp_avail); |
| |
| rsp = virtqueue_get_buf(vreg->vq, &len); |
| if (!rsp) { |
| pr_err("%s: fail to get virtqueue buffer\n", reg->rdesc.name); |
| ret = -EIO; |
| goto out; |
| } |
| |
| ret = virtio32_to_cpu(vreg->vdev, rsp->result); |
| |
| out: |
| mutex_unlock(&vreg->lock); |
| kfree(req); |
| |
| pr_debug("%s return %d\n", reg->rdesc.name, ret); |
| |
| return ret; |
| } |
| |
| static unsigned int virtio_regulator_get_mode(struct regulator_dev *rdev) |
| { |
| struct reg_virtio *reg = rdev_get_drvdata(rdev); |
| struct virtio_regulator *vreg = reg->vreg; |
| struct virtio_regulator_msg *req, *rsp; |
| struct scatterlist sg[1]; |
| unsigned int len; |
| int ret = 0; |
| |
| req = kzalloc(sizeof(struct virtio_regulator_msg), GFP_KERNEL); |
| if (!req) |
| return -ENOMEM; |
| |
| strlcpy(req->name, reg->rdesc.name, sizeof(req->name)); |
| req->type = cpu_to_virtio32(vreg->vdev, VIRTIO_REGULATOR_T_GET_MODE); |
| sg_init_one(sg, req, sizeof(*req)); |
| |
| mutex_lock(&vreg->lock); |
| |
| ret = virtqueue_add_outbuf(vreg->vq, sg, 1, req, GFP_KERNEL); |
| if (ret) { |
| pr_err("%s: fail to add output buffer\n", reg->rdesc.name); |
| goto out; |
| } |
| |
| virtqueue_kick(vreg->vq); |
| |
| wait_for_completion(&vreg->rsp_avail); |
| |
| rsp = virtqueue_get_buf(vreg->vq, &len); |
| if (!rsp) { |
| pr_err("%s: fail to get virtqueue buffer\n", reg->rdesc.name); |
| ret = -EIO; |
| goto out; |
| } |
| |
| if (rsp->result) { |
| pr_err("%s: error response (%d)\n", reg->rdesc.name, |
| virtio32_to_cpu(vreg->vdev, rsp->result)); |
| ret = 0; |
| } else |
| ret = virtio32_to_cpu(vreg->vdev, rsp->data[0]); |
| |
| out: |
| mutex_unlock(&vreg->lock); |
| kfree(req); |
| |
| pr_debug("%s return %d\n", reg->rdesc.name, ret); |
| |
| return ret; |
| } |
| |
| static int virtio_regulator_set_load(struct regulator_dev *rdev, int load_ua) |
| { |
| struct reg_virtio *reg = rdev_get_drvdata(rdev); |
| struct virtio_regulator *vreg = reg->vreg; |
| struct virtio_regulator_msg *req, *rsp; |
| struct scatterlist sg[1]; |
| unsigned int len; |
| int ret = 0; |
| |
| req = kzalloc(sizeof(struct virtio_regulator_msg), GFP_KERNEL); |
| if (!req) |
| return -ENOMEM; |
| |
| strlcpy(req->name, reg->rdesc.name, sizeof(req->name)); |
| req->type = cpu_to_virtio32(vreg->vdev, VIRTIO_REGULATOR_T_SET_LOAD); |
| req->data[0] = cpu_to_virtio32(vreg->vdev, load_ua); |
| sg_init_one(sg, req, sizeof(*req)); |
| |
| mutex_lock(&vreg->lock); |
| |
| ret = virtqueue_add_outbuf(vreg->vq, sg, 1, req, GFP_KERNEL); |
| if (ret) { |
| pr_err("%s: fail to add output buffer\n", reg->rdesc.name); |
| goto out; |
| } |
| |
| virtqueue_kick(vreg->vq); |
| |
| wait_for_completion(&vreg->rsp_avail); |
| |
| rsp = virtqueue_get_buf(vreg->vq, &len); |
| if (!rsp) { |
| pr_err("%s: fail to get virtqueue buffer\n", reg->rdesc.name); |
| ret = -EIO; |
| goto out; |
| } |
| |
| ret = virtio32_to_cpu(vreg->vdev, rsp->result); |
| |
| out: |
| mutex_unlock(&vreg->lock); |
| kfree(req); |
| |
| pr_debug("%s return %d\n", reg->rdesc.name, ret); |
| |
| return ret; |
| } |
| |
| static struct regulator_ops virtio_regulator_ops = { |
| .enable = virtio_regulator_enable, |
| .disable = virtio_regulator_disable, |
| .is_enabled = virtio_regulator_is_enabled, |
| .set_voltage = virtio_regulator_set_voltage, |
| .get_voltage = virtio_regulator_get_voltage, |
| .set_mode = virtio_regulator_set_mode, |
| .get_mode = virtio_regulator_get_mode, |
| .set_load = virtio_regulator_set_load, |
| }; |
| |
| static void virtio_regulator_isr(struct virtqueue *vq) |
| { |
| struct virtio_regulator *vregulator = vq->vdev->priv; |
| |
| complete(&vregulator->rsp_avail); |
| } |
| |
| static int virtio_regulator_init_vqs(struct virtio_regulator *vreg) |
| { |
| struct virtqueue *vqs[1]; |
| vq_callback_t *cbs[] = { virtio_regulator_isr }; |
| static const char * const names[] = { "regulator" }; |
| int ret; |
| |
| ret = virtio_find_vqs(vreg->vdev, 1, vqs, cbs, names, NULL); |
| if (ret) |
| return ret; |
| |
| vreg->vq = vqs[0]; |
| |
| return 0; |
| } |
| |
| static int virtio_regulator_allocate_reg(struct virtio_regulator *vreg) |
| { |
| struct device_node *parent_node, *node; |
| int i, ret; |
| |
| vreg->regs_count = 0; |
| parent_node = vreg->vdev->dev.parent->of_node; |
| |
| for_each_available_child_of_node(parent_node, node) { |
| /* Skip child nodes handled by other drivers. */ |
| if (of_find_property(node, "compatible", NULL)) |
| continue; |
| vreg->regs_count++; |
| } |
| |
| if (vreg->regs_count == 0) { |
| dev_err(&vreg->vdev->dev, |
| "could not find any regulator subnodes\n"); |
| return -ENODEV; |
| } |
| |
| vreg->regs = devm_kcalloc(&vreg->vdev->dev, vreg->regs_count, |
| sizeof(*vreg->regs), GFP_KERNEL); |
| if (!vreg->regs) |
| return -ENOMEM; |
| |
| i = 0; |
| for_each_available_child_of_node(parent_node, node) { |
| /* Skip child nodes handled by other drivers. */ |
| if (of_find_property(node, "compatible", NULL)) |
| continue; |
| |
| vreg->regs[i].of_node = node; |
| vreg->regs[i].vreg = vreg; |
| |
| ret = of_property_read_string(node, "regulator-name", |
| &vreg->regs[i].rdesc.name); |
| if (ret) { |
| dev_err(&vreg->vdev->dev, |
| "could not read regulator-name\n"); |
| return ret; |
| } |
| |
| i++; |
| } |
| |
| return 0; |
| } |
| |
| static int virtio_regulator_init_reg(struct reg_virtio *reg) |
| { |
| struct device *dev = reg->vreg->vdev->dev.parent; |
| struct regulator_config reg_config = {}; |
| struct regulator_init_data *init_data; |
| int ret = 0; |
| |
| reg->rdesc.owner = THIS_MODULE; |
| reg->rdesc.type = REGULATOR_VOLTAGE; |
| reg->rdesc.ops = &virtio_regulator_ops; |
| |
| init_data = of_get_regulator_init_data(dev, reg->of_node, ®->rdesc); |
| if (init_data == NULL) |
| return -ENOMEM; |
| |
| init_data->constraints.input_uV = init_data->constraints.max_uV; |
| init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE; |
| |
| reg_config.dev = dev; |
| reg_config.init_data = init_data; |
| reg_config.of_node = reg->of_node; |
| reg_config.driver_data = reg; |
| |
| reg->rdev = devm_regulator_register(dev, ®->rdesc, ®_config); |
| if (IS_ERR(reg->rdev)) { |
| ret = PTR_ERR(reg->rdev); |
| reg->rdev = NULL; |
| dev_err(®->vreg->vdev->dev, "fail to register regulator\n"); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int virtio_regulator_probe(struct virtio_device *vdev) |
| { |
| struct virtio_regulator *vreg; |
| unsigned int i; |
| int ret; |
| |
| if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) |
| return -ENODEV; |
| |
| vreg = devm_kzalloc(&vdev->dev, sizeof(struct virtio_regulator), |
| GFP_KERNEL); |
| if (!vreg) |
| return -ENOMEM; |
| |
| vdev->priv = vreg; |
| vreg->vdev = vdev; |
| mutex_init(&vreg->lock); |
| init_completion(&vreg->rsp_avail); |
| |
| ret = virtio_regulator_init_vqs(vreg); |
| if (ret) { |
| dev_err(&vdev->dev, "fail to initialize virtqueue\n"); |
| return ret; |
| } |
| |
| virtio_device_ready(vdev); |
| |
| ret = virtio_regulator_allocate_reg(vreg); |
| if (ret) { |
| dev_err(&vdev->dev, "fail to allocate regulators\n"); |
| goto err_allocate_reg; |
| } |
| |
| for (i = 0; i < vreg->regs_count; i++) { |
| ret = virtio_regulator_init_reg(&vreg->regs[i]); |
| if (ret) { |
| dev_err(&vdev->dev, "fail to initialize regulator %s\n", |
| vreg->regs[i].rdesc.name); |
| goto err_init_reg; |
| } |
| } |
| |
| dev_dbg(&vdev->dev, "virtio regulator probe successfully\n"); |
| |
| return 0; |
| |
| err_init_reg: |
| err_allocate_reg: |
| vdev->config->del_vqs(vdev); |
| return ret; |
| } |
| |
| static void virtio_regulator_remove(struct virtio_device *vdev) |
| { |
| struct virtio_regulator *vreg = vdev->priv; |
| void *buf; |
| |
| vdev->config->reset(vdev); |
| while ((buf = virtqueue_detach_unused_buf(vreg->vq)) != NULL) |
| kfree(buf); |
| vdev->config->del_vqs(vdev); |
| } |
| |
| static const struct virtio_device_id id_table[] = { |
| { VIRTIO_ID_REGULATOR, VIRTIO_DEV_ANY_ID }, |
| { 0 }, |
| }; |
| |
| static unsigned int features[] = { |
| }; |
| |
| static struct virtio_driver virtio_regulator_driver = { |
| .feature_table = features, |
| .feature_table_size = ARRAY_SIZE(features), |
| .driver.name = KBUILD_MODNAME, |
| .driver.owner = THIS_MODULE, |
| .id_table = id_table, |
| .probe = virtio_regulator_probe, |
| .remove = virtio_regulator_remove, |
| }; |
| |
| static int __init virtio_regulator_init(void) |
| { |
| return register_virtio_driver(&virtio_regulator_driver); |
| } |
| |
| static void __exit virtio_regulator_exit(void) |
| { |
| unregister_virtio_driver(&virtio_regulator_driver); |
| } |
| subsys_initcall(virtio_regulator_init); |
| module_exit(virtio_regulator_exit); |
| |
| MODULE_DEVICE_TABLE(virtio, id_table); |
| MODULE_DESCRIPTION("Virtio regulator driver"); |
| MODULE_LICENSE("GPL v2"); |