Message ID | 1412037291-16880-7-git-send-email-bjorn.andersson@sonymobile.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Mon, 29 Sep 2014, Bjorn Andersson wrote: > Driver for the Resource Power Manager (RPM) found in Qualcomm 8974 based > devices. > The driver exposes resources that child drivers can operate on; to > implementing regulator, clock and bus frequency drivers. > > Signed-off-by: Bjorn Andersson <bjorn.andersson@sonymobile.com> > --- > > Note that the qcom_rpm_smd_write is equivalent of qcom_rpm_write and there is a > possibility of re-using at least the clock implementation on top of this. This > would however require some logic for calling the right implementation so I have > not done it at this time to keep things as clean as possible. > > An idea for improvement is that in qcom_rpm_smd_write we put the ack_status and > completion on the stack and register this with idr using the message id, upon > receiving the interrupt we would find the right client and complete this. > Allowing for multiple requests to be in flight at any given time. > > I did not implement this because I haven't done any measurements on what kind > of improvements this could give and it would be a clean iteration ontop of > this. > > drivers/mfd/Kconfig | 14 ++ > drivers/mfd/Makefile | 1 + > drivers/mfd/qcom-smd-rpm.c | 299 ++++++++++++++++++++++++++++++++++++++ > include/linux/mfd/qcom-smd-rpm.h | 9 ++ > 4 files changed, 323 insertions(+) > create mode 100644 drivers/mfd/qcom-smd-rpm.c > create mode 100644 include/linux/mfd/qcom-smd-rpm.h > +#define RPM_ERR_INVALID_RESOURCE "resource does not exist" > + > +static bool qcom_rpm_msg_is_invalid_resource(struct qcom_rpm_message *msg) > +{ > + size_t msg_len = sizeof(RPM_ERR_INVALID_RESOURCE) - 1; > + > + if (msg->length != msg_len) > + return false; > + > + if (memcmp(msg->message, RPM_ERR_INVALID_RESOURCE, msg_len)) > + return false; > + > + return true; > +} You can save yourself a hell of a lot of code by just doing: if (memcmp(msg->message, RPM_ERR_INVALID_RESOURCE, min(msg_len, sizeof(RPM_ERR_INVALID_RESOURCE)))) ... in qcom_smd_rpm_callback(). [...] > +static int qcom_smd_rpm_probe(struct qcom_smd_device *sdev) > +{ > + const struct of_device_id *match; > + struct qcom_smd_rpm *rpm; > + > + rpm = devm_kzalloc(&sdev->dev, sizeof(*rpm), GFP_KERNEL); > + if (!rpm) > + return -ENOMEM; > + > + rpm->dev = &sdev->dev; > + mutex_init(&rpm->lock); > + init_completion(&rpm->ack); > + > + match = of_match_device(qcom_smd_rpm_of_match, &sdev->dev); You need to check the return value here. > + rpm->data = match->data; > + rpm->rpm_channel = sdev->channel; > + > + dev_set_drvdata(&sdev->dev, rpm); > + > + dev_info(&sdev->dev, "Qualcomm SMD RPM driver probed\n"); Please remove this line. > + return of_platform_populate(sdev->dev.of_node, NULL, NULL, &sdev->dev); > +} > + > +static void qcom_smd_rpm_remove(struct qcom_smd_device *sdev) > +{ > + dev_set_drvdata(&sdev->dev, NULL); If you use the proper platform device interface you don't have to do this. > + of_platform_depopulate(&sdev->dev); > +} > + > +static struct qcom_smd_driver qcom_smd_rpm_driver = { > + .probe = qcom_smd_rpm_probe, > + .remove = qcom_smd_rpm_remove, > + .callback = qcom_smd_rpm_callback, > + .driver = { > + .name = "qcom_smd_rpm", > + .owner = THIS_MODULE, > + .of_match_table = qcom_smd_rpm_of_match, > + }, > +}; > + > +module_qcom_smd_driver(qcom_smd_rpm_driver); I don't like this. What's wrong with the existing platform driver code?
On Wed 08 Oct 01:40 PDT 2014, Lee Jones wrote: Hi Lee, Thanks for your review. > On Mon, 29 Sep 2014, Bjorn Andersson wrote: > [..] > > +#define RPM_ERR_INVALID_RESOURCE "resource does not exist" > > + > > +static bool qcom_rpm_msg_is_invalid_resource(struct qcom_rpm_message *msg) > > +{ > > + size_t msg_len = sizeof(RPM_ERR_INVALID_RESOURCE) - 1; > > + > > + if (msg->length != msg_len) > > + return false; > > + > > + if (memcmp(msg->message, RPM_ERR_INVALID_RESOURCE, msg_len)) > > + return false; > > + > > + return true; > > +} > > You can save yourself a hell of a lot of code by just doing: > > if (memcmp(msg->message, RPM_ERR_INVALID_RESOURCE, > min(msg_len, sizeof(RPM_ERR_INVALID_RESOURCE)))) > > ... in qcom_smd_rpm_callback(). > I can agree with you that there will be less code, but not "a hell of a lot". I made the choise because I had something like the snippet you suggest and I wanted to make it cleaner - I'll fold it back in. > [...] > > > +static int qcom_smd_rpm_probe(struct qcom_smd_device *sdev) > > +{ > > + const struct of_device_id *match; > > + struct qcom_smd_rpm *rpm; > > + > > + rpm = devm_kzalloc(&sdev->dev, sizeof(*rpm), GFP_KERNEL); > > + if (!rpm) > > + return -ENOMEM; > > + > > + rpm->dev = &sdev->dev; > > + mutex_init(&rpm->lock); > > + init_completion(&rpm->ack); > > + > > + match = of_match_device(qcom_smd_rpm_of_match, &sdev->dev); > > You need to check the return value here. > As long as we only support device tree probing of this match will never return NULL. I can add a check to fail on non-dt boards if someone chooses to ever implement one of those. > > + rpm->data = match->data; > > + rpm->rpm_channel = sdev->channel; > > + > > + dev_set_drvdata(&sdev->dev, rpm); > > + > > + dev_info(&sdev->dev, "Qualcomm SMD RPM driver probed\n"); > > Please remove this line. > Ok > > + return of_platform_populate(sdev->dev.of_node, NULL, NULL, &sdev->dev); > > +} > > + > > +static void qcom_smd_rpm_remove(struct qcom_smd_device *sdev) > > +{ > > + dev_set_drvdata(&sdev->dev, NULL); > > If you use the proper platform device interface you don't have to do > this. > Ok > > + of_platform_depopulate(&sdev->dev); > > +} > > + > > +static struct qcom_smd_driver qcom_smd_rpm_driver = { > > + .probe = qcom_smd_rpm_probe, > > + .remove = qcom_smd_rpm_remove, > > + .callback = qcom_smd_rpm_callback, > > + .driver = { > > + .name = "qcom_smd_rpm", > > + .owner = THIS_MODULE, > > + .of_match_table = qcom_smd_rpm_of_match, > > + }, > > +}; > > + > > +module_qcom_smd_driver(qcom_smd_rpm_driver); > > I don't like this. What's wrong with the existing platform driver > code? > I started off with having smd child devices as platform drivers and had some accessor functions to find the open handles that triggered the probe() and register the callback with those. But this didn't feel very sane, so I did implemented a custom driver struct and probe prototype to simplify writing drivers. May I ask why you dislike this? This is how it's done in so many other places in the kernel... Regards, Bjorn
On Fri, 17 Oct 2014, Bjorn Andersson wrote: > On Wed 08 Oct 01:40 PDT 2014, Lee Jones wrote: [...] > > > +static struct qcom_smd_driver qcom_smd_rpm_driver = { > > > + .probe = qcom_smd_rpm_probe, > > > + .remove = qcom_smd_rpm_remove, > > > + .callback = qcom_smd_rpm_callback, > > > + .driver = { > > > + .name = "qcom_smd_rpm", > > > + .owner = THIS_MODULE, > > > + .of_match_table = qcom_smd_rpm_of_match, > > > + }, > > > +}; > > > + > > > +module_qcom_smd_driver(qcom_smd_rpm_driver); > > > > I don't like this. What's wrong with the existing platform driver > > code? > > > > I started off with having smd child devices as platform drivers and had some > accessor functions to find the open handles that triggered the probe() and > register the callback with those. But this didn't feel very sane, so I did > implemented a custom driver struct and probe prototype to simplify writing > drivers. > > May I ask why you dislike this? This is how it's done in so many other places > in the kernel... I don't believe that's the case. All owners of their own module_*_driver() registration calls are busses (see below), whereas 'qcom_smd' is just a driver. Things would soon get out of control if we allowed every driver in the kernel to supply their own driver registration information variants. $ git grep "^module_.*_driver(" | \ cut -d: -f2 | cut -d'(' -f1 | sort | uniq module_acpi_driver module_amba_driver module_comedi_driver module_comedi_pci_driver module_comedi_pcmcia_driver module_comedi_usb_driver module_gameport_driver module_hid_driver module_i2c_driver module_mcb_driver module_mipi_dsi_driver module_pci_driver module_pcmcia_driver module_platform_driver module_serio_driver module_spi_driver module_spmi_driver module_usb_composite_driver module_usb_driver module_usb_serial_driver module_virtio_driver
On Mon 20 Oct 00:22 PDT 2014, Lee Jones wrote: > On Fri, 17 Oct 2014, Bjorn Andersson wrote: > > On Wed 08 Oct 01:40 PDT 2014, Lee Jones wrote: > > [...] > > > > > +static struct qcom_smd_driver qcom_smd_rpm_driver = { > > > > + .probe = qcom_smd_rpm_probe, > > > > + .remove = qcom_smd_rpm_remove, > > > > + .callback = qcom_smd_rpm_callback, > > > > + .driver = { > > > > + .name = "qcom_smd_rpm", > > > > + .owner = THIS_MODULE, > > > > + .of_match_table = qcom_smd_rpm_of_match, > > > > + }, > > > > +}; > > > > + > > > > +module_qcom_smd_driver(qcom_smd_rpm_driver); > > > > > > I don't like this. What's wrong with the existing platform driver > > > code? > > > > > > > I started off with having smd child devices as platform drivers and had some > > accessor functions to find the open handles that triggered the probe() and > > register the callback with those. But this didn't feel very sane, so I did > > implemented a custom driver struct and probe prototype to simplify writing > > drivers. > > > > May I ask why you dislike this? This is how it's done in so many other places > > in the kernel... > > I don't believe that's the case. All owners of their own > module_*_driver() registration calls are busses (see below), whereas > 'qcom_smd' is just a driver. Things would soon get out of control if > we allowed every driver in the kernel to supply their own driver > registration information variants. > I modelled this after rpmsg, with the intention of having qcom_smd provide a "smd bus" and all client drivers sitting on that bus being probed and removed as the remote services appear and disappear. I'm afraid I don't understand what part I missed that makes my smd driver "just a driver". I will reread the documentation and try to figure out what I might have missed. Regards, Bjorn
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 6743e88..c62c7f5 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -553,6 +553,20 @@ config MFD_QCOM_RPM Say M here if you want to include support for the Qualcomm RPM as a module. This will build a module called "qcom_rpm". +config MFD_QCOM_SMD_RPM + tristate "Qualcomm Resource Power Manager (RPM) over SMD" + depends on QCOM_SMD && OF + help + If you say yes to this option, support will be included for the + Resource Power Manager system found in the Qualcomm 8974 based + devices. + + This is required to access many regulators, clocks and bus + frequencies controlled by the RPM on these devices. + + Say M here if you want to include support for the Qualcomm RPM as a + module. This will build a module called "qcom-smd-rpm". + config MFD_RDC321X tristate "RDC R-321x southbridge" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 3f2fc89..e19ab12 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -154,6 +154,7 @@ obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o omap-usb-tll.o obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o ssbi.o obj-$(CONFIG_MFD_QCOM_RPM) += qcom_rpm.o +obj-$(CONFIG_MFD_QCOM_SMD_RPM) += qcom-smd-rpm.o obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o obj-$(CONFIG_MFD_TPS65090) += tps65090.o obj-$(CONFIG_MFD_AAT2870_CORE) += aat2870-core.o diff --git a/drivers/mfd/qcom-smd-rpm.c b/drivers/mfd/qcom-smd-rpm.c new file mode 100644 index 0000000..3f77f87 --- /dev/null +++ b/drivers/mfd/qcom-smd-rpm.c @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2014, Sony Mobile Communications AB. + * Copyright (c) 2012-2013, 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. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/io.h> +#include <linux/interrupt.h> + +#include <linux/soc/qcom/qcom_smd.h> +#include <linux/mfd/qcom-smd-rpm.h> + +#include <dt-bindings/mfd/qcom-rpm.h> + +#define RPM_REQUEST_TIMEOUT (5 * HZ) + +struct qcom_rpm_resource { + u32 resource_type; + u32 resource_id; +}; + +struct qcom_rpm_data { + const struct qcom_rpm_resource *resource_table; + unsigned nresources; +}; + +struct qcom_smd_rpm { + struct device *dev; + struct qcom_smd_channel *rpm_channel; + + struct completion ack; + struct mutex lock; + int ack_status; + + const struct qcom_rpm_data *data; +}; + +struct qcom_rpm_header { + u32 service_type; + u32 length; +}; + +struct qcom_rpm_request { + u32 msg_id; + u32 flags; + u32 resource_type; + u32 resource_id; + u32 data_len; +}; + +struct qcom_rpm_message { + u32 msg_type; + u32 length; + union { + u32 msg_id; + u8 message[0]; + }; +}; + +#define RPM_SERVICE_TYPE_REQUEST 0x00716572 /* "req\0" */ + +#define RPM_MSG_TYPE_ERR 0x00727265 /* "err\0" */ +#define RPM_MSG_TYPE_MSG_ID 0x2367736d /* "msg#" */ + +#define RESOURCE_TYPE_SMPA 0x61706d73 /* "smpa" */ +#define RESOURCE_TYPE_SMPB 0x62706d73 /* "smpb" */ +#define RESOURCE_TYPE_LDOA 0x616f646c /* "ldoa" */ +#define RESOURCE_TYPE_VSA 0x00617376 /* "vsa\0" */ + +#define RPM_MSG_FLAGS_SET_ACTIVE_MODE BIT(0) +#define RPM_MSG_FLAGS_SET_SLEEP_MODE BIT(1) + +static const struct qcom_rpm_resource msm8x74_resource_table[] = { + [QCOM_RPM_PM8841_SMPS1] = { RESOURCE_TYPE_SMPB, 1 }, + [QCOM_RPM_PM8841_SMPS2] = { RESOURCE_TYPE_SMPB, 2 }, + [QCOM_RPM_PM8841_SMPS3] = { RESOURCE_TYPE_SMPB, 3 }, + [QCOM_RPM_PM8841_SMPS4] = { RESOURCE_TYPE_SMPB, 4 }, + + [QCOM_RPM_PM8941_SMPS1] = { RESOURCE_TYPE_SMPA, 1 }, + [QCOM_RPM_PM8941_SMPS2] = { RESOURCE_TYPE_SMPA, 2 }, + [QCOM_RPM_PM8941_SMPS3] = { RESOURCE_TYPE_SMPA, 3 }, + + [QCOM_RPM_PM8941_LDO1] = { RESOURCE_TYPE_LDOA, 1 }, + [QCOM_RPM_PM8941_LDO2] = { RESOURCE_TYPE_LDOA, 2 }, + [QCOM_RPM_PM8941_LDO3] = { RESOURCE_TYPE_LDOA, 3 }, + [QCOM_RPM_PM8941_LDO4] = { RESOURCE_TYPE_LDOA, 4 }, + [QCOM_RPM_PM8941_LDO5] = { RESOURCE_TYPE_LDOA, 5 }, + [QCOM_RPM_PM8941_LDO6] = { RESOURCE_TYPE_LDOA, 6 }, + [QCOM_RPM_PM8941_LDO7] = { RESOURCE_TYPE_LDOA, 7 }, + [QCOM_RPM_PM8941_LDO8] = { RESOURCE_TYPE_LDOA, 8 }, + [QCOM_RPM_PM8941_LDO9] = { RESOURCE_TYPE_LDOA, 9 }, + [QCOM_RPM_PM8941_LDO10] = { RESOURCE_TYPE_LDOA, 10 }, + [QCOM_RPM_PM8941_LDO11] = { RESOURCE_TYPE_LDOA, 11 }, + [QCOM_RPM_PM8941_LDO12] = { RESOURCE_TYPE_LDOA, 12 }, + [QCOM_RPM_PM8941_LDO13] = { RESOURCE_TYPE_LDOA, 13 }, + [QCOM_RPM_PM8941_LDO14] = { RESOURCE_TYPE_LDOA, 14 }, + [QCOM_RPM_PM8941_LDO15] = { RESOURCE_TYPE_LDOA, 15 }, + [QCOM_RPM_PM8941_LDO16] = { RESOURCE_TYPE_LDOA, 16 }, + [QCOM_RPM_PM8941_LDO17] = { RESOURCE_TYPE_LDOA, 17 }, + [QCOM_RPM_PM8941_LDO18] = { RESOURCE_TYPE_LDOA, 18 }, + [QCOM_RPM_PM8941_LDO19] = { RESOURCE_TYPE_LDOA, 19 }, + [QCOM_RPM_PM8941_LDO20] = { RESOURCE_TYPE_LDOA, 20 }, + [QCOM_RPM_PM8941_LDO21] = { RESOURCE_TYPE_LDOA, 21 }, + [QCOM_RPM_PM8941_LDO22] = { RESOURCE_TYPE_LDOA, 22 }, + [QCOM_RPM_PM8941_LDO23] = { RESOURCE_TYPE_LDOA, 23 }, + [QCOM_RPM_PM8941_LDO24] = { RESOURCE_TYPE_LDOA, 24 }, + + [QCOM_RPM_PM8941_LVS1] = { RESOURCE_TYPE_VSA, 1 }, + [QCOM_RPM_PM8941_LVS2] = { RESOURCE_TYPE_VSA, 2 }, + [QCOM_RPM_PM8941_LVS3] = { RESOURCE_TYPE_VSA, 3 }, + + [QCOM_RPM_PM8941_MVS1] = { RESOURCE_TYPE_VSA, 4 }, + [QCOM_RPM_PM8941_MVS2] = { RESOURCE_TYPE_VSA, 5 }, +}; + +static const struct qcom_rpm_data msm8x74_template = { + .resource_table = msm8x74_resource_table, + .nresources = ARRAY_SIZE(msm8x74_resource_table), +}; + +static const struct of_device_id qcom_smd_rpm_of_match[] = { + { .compatible = "qcom,rpm-msm8974", .data = &msm8x74_template }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match); + +/** + * qcom_rpm_smd_write - write @buf to @resource + * @rpm: rpm handle + * @resource: resource identifier + * @buf: the data to be written + * @count: number of bytes in @buf + */ +int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm, + int resource, + void *buf, + size_t count) +{ + const struct qcom_rpm_resource *res; + const struct qcom_rpm_data *data = rpm->data; + static unsigned msg_id = 1; + int left; + int ret; + + struct { + struct qcom_rpm_header hdr; + struct qcom_rpm_request req; + u8 payload[count]; + } pkt; + + /* SMD packets to the RPM may not exceed 256 bytes */ + if (WARN_ON(sizeof(pkt) >= 256)) + return -EINVAL; + + if (WARN_ON(resource < 0 || resource >= data->nresources)) + return -EINVAL; + + res = &data->resource_table[resource]; + if (WARN_ON(!res->resource_id || !res->resource_type)) + return -EINVAL; + + mutex_lock(&rpm->lock); + + pkt.hdr.service_type = RPM_SERVICE_TYPE_REQUEST; + pkt.hdr.length = sizeof(struct qcom_rpm_request) + count; + + pkt.req.msg_id = msg_id++; + pkt.req.flags = RPM_MSG_FLAGS_SET_ACTIVE_MODE; + pkt.req.resource_type = res->resource_type; + pkt.req.resource_id = res->resource_id; + pkt.req.data_len = count; + memcpy(pkt.payload, buf, count); + + ret = qcom_smd_send(rpm->rpm_channel, &pkt, sizeof(pkt)); + if (ret) + goto out; + + left = wait_for_completion_timeout(&rpm->ack, RPM_REQUEST_TIMEOUT); + if (!left) + ret = -ETIMEDOUT; + else + ret = rpm->ack_status; + +out: + mutex_unlock(&rpm->lock); + return ret; +} +EXPORT_SYMBOL(qcom_rpm_smd_write); + +#define RPM_ERR_INVALID_RESOURCE "resource does not exist" + +static bool qcom_rpm_msg_is_invalid_resource(struct qcom_rpm_message *msg) +{ + size_t msg_len = sizeof(RPM_ERR_INVALID_RESOURCE) - 1; + + if (msg->length != msg_len) + return false; + + if (memcmp(msg->message, RPM_ERR_INVALID_RESOURCE, msg_len)) + return false; + + return true; +} + +static int qcom_smd_rpm_callback(struct qcom_smd_device *qsdev, + void *data, + size_t count) +{ + struct qcom_rpm_header *hdr = data; + struct qcom_rpm_message *msg; + struct qcom_smd_rpm *rpm = dev_get_drvdata(&qsdev->dev); + u8 *buf = data + sizeof(struct qcom_rpm_header); + u8 *end = buf + hdr->length; + int status = 0; + + if (hdr->service_type != RPM_SERVICE_TYPE_REQUEST || + hdr->length < sizeof(struct qcom_rpm_message)) { + dev_err(rpm->dev, "invalid request\n"); + return 0; + } + + while (buf < end) { + msg = (struct qcom_rpm_message *)buf; + switch (msg->msg_type) { + case RPM_MSG_TYPE_MSG_ID: + break; + case RPM_MSG_TYPE_ERR: + if (qcom_rpm_msg_is_invalid_resource(msg)) + status = -ENXIO; + else + status = -EIO; + break; + } + + buf = PTR_ALIGN(buf + 2 * sizeof(u32) + msg->length, 4); + } + + rpm->ack_status = status; + complete(&rpm->ack); + return 0; +} + +static int qcom_smd_rpm_probe(struct qcom_smd_device *sdev) +{ + const struct of_device_id *match; + struct qcom_smd_rpm *rpm; + + rpm = devm_kzalloc(&sdev->dev, sizeof(*rpm), GFP_KERNEL); + if (!rpm) + return -ENOMEM; + + rpm->dev = &sdev->dev; + mutex_init(&rpm->lock); + init_completion(&rpm->ack); + + match = of_match_device(qcom_smd_rpm_of_match, &sdev->dev); + rpm->data = match->data; + rpm->rpm_channel = sdev->channel; + + dev_set_drvdata(&sdev->dev, rpm); + + dev_info(&sdev->dev, "Qualcomm SMD RPM driver probed\n"); + + return of_platform_populate(sdev->dev.of_node, NULL, NULL, &sdev->dev); +} + +static void qcom_smd_rpm_remove(struct qcom_smd_device *sdev) +{ + dev_set_drvdata(&sdev->dev, NULL); + of_platform_depopulate(&sdev->dev); +} + +static struct qcom_smd_driver qcom_smd_rpm_driver = { + .probe = qcom_smd_rpm_probe, + .remove = qcom_smd_rpm_remove, + .callback = qcom_smd_rpm_callback, + .driver = { + .name = "qcom_smd_rpm", + .owner = THIS_MODULE, + .of_match_table = qcom_smd_rpm_of_match, + }, +}; + +module_qcom_smd_driver(qcom_smd_rpm_driver); + +MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); +MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver"); +MODULE_LICENSE("GPLv2"); diff --git a/include/linux/mfd/qcom-smd-rpm.h b/include/linux/mfd/qcom-smd-rpm.h new file mode 100644 index 0000000..59b9425 --- /dev/null +++ b/include/linux/mfd/qcom-smd-rpm.h @@ -0,0 +1,9 @@ +#ifndef __QCOM_SMD_RPM_H__ +#define __QCOM_SMD_RPM_H__ + +struct qcom_smd_rpm; + +int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm, int resource, + void *buf, size_t count); + +#endif
Driver for the Resource Power Manager (RPM) found in Qualcomm 8974 based devices. The driver exposes resources that child drivers can operate on; to implementing regulator, clock and bus frequency drivers. Signed-off-by: Bjorn Andersson <bjorn.andersson@sonymobile.com> --- Note that the qcom_rpm_smd_write is equivalent of qcom_rpm_write and there is a possibility of re-using at least the clock implementation on top of this. This would however require some logic for calling the right implementation so I have not done it at this time to keep things as clean as possible. An idea for improvement is that in qcom_rpm_smd_write we put the ack_status and completion on the stack and register this with idr using the message id, upon receiving the interrupt we would find the right client and complete this. Allowing for multiple requests to be in flight at any given time. I did not implement this because I haven't done any measurements on what kind of improvements this could give and it would be a clean iteration ontop of this. drivers/mfd/Kconfig | 14 ++ drivers/mfd/Makefile | 1 + drivers/mfd/qcom-smd-rpm.c | 299 ++++++++++++++++++++++++++++++++++++++ include/linux/mfd/qcom-smd-rpm.h | 9 ++ 4 files changed, 323 insertions(+) create mode 100644 drivers/mfd/qcom-smd-rpm.c create mode 100644 include/linux/mfd/qcom-smd-rpm.h