11d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport/*
21d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport * userspace-consumer.c
31d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport *
41d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport * Copyright 2009 CompuLab, Ltd.
51d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport *
61d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport * Author: Mike Rapoport <mike@compulab.co.il>
71d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport *
81d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport * Based of virtual consumer driver:
91d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport *   Copyright 2008 Wolfson Microelectronics PLC.
101d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport *   Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
111d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport *
121d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport * This program is free software; you can redistribute it and/or
131d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport * modify it under the terms of the GNU General Public License as
141d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport * published by the Free Software Foundation; either version 2 of the
151d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport * License, or (at your option) any later version.
161d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport *
171d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport */
181d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
191d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport#include <linux/err.h>
201d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport#include <linux/mutex.h>
2165602c32ee9b5500e3cb617ccec2154ee2191898Paul Gortmaker#include <linux/module.h>
221d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport#include <linux/platform_device.h>
231d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport#include <linux/regulator/consumer.h>
241d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport#include <linux/regulator/userspace-consumer.h>
255a0e3ad6af8660be21ca98a971cd00f331318c05Tejun Heo#include <linux/slab.h>
261d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
271d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoportstruct userspace_consumer_data {
281d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	const char *name;
291d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
301d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	struct mutex lock;
311d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	bool enabled;
321d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
331d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	int num_supplies;
341d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	struct regulator_bulk_data *supplies;
351d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport};
361d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
377c314991d7d7ad4edf96e8322bcb30e8452957b7Liam Girdwoodstatic ssize_t reg_show_name(struct device *dev,
381d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport			  struct device_attribute *attr, char *buf)
391d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport{
401d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	struct userspace_consumer_data *data = dev_get_drvdata(dev);
411d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
421d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	return sprintf(buf, "%s\n", data->name);
431d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport}
441d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
457c314991d7d7ad4edf96e8322bcb30e8452957b7Liam Girdwoodstatic ssize_t reg_show_state(struct device *dev,
461d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport			  struct device_attribute *attr, char *buf)
471d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport{
481d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	struct userspace_consumer_data *data = dev_get_drvdata(dev);
491d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
501d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	if (data->enabled)
511d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		return sprintf(buf, "enabled\n");
521d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
531d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	return sprintf(buf, "disabled\n");
541d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport}
551d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
567c314991d7d7ad4edf96e8322bcb30e8452957b7Liam Girdwoodstatic ssize_t reg_set_state(struct device *dev, struct device_attribute *attr,
571d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport			 const char *buf, size_t count)
581d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport{
591d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	struct userspace_consumer_data *data = dev_get_drvdata(dev);
601d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	bool enabled;
611d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	int ret;
621d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
631d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	/*
641d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	 * sysfs_streq() doesn't need the \n's, but we add them so the strings
651d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	 * will be shared with show_state(), above.
661d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	 */
671d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	if (sysfs_streq(buf, "enabled\n") || sysfs_streq(buf, "1"))
681d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		enabled = true;
691d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	else if (sysfs_streq(buf, "disabled\n") || sysfs_streq(buf, "0"))
701d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		enabled = false;
711d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	else {
721d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		dev_err(dev, "Configuring invalid mode\n");
731d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		return count;
741d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	}
751d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
761d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	mutex_lock(&data->lock);
771d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	if (enabled != data->enabled) {
781d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		if (enabled)
791d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport			ret = regulator_bulk_enable(data->num_supplies,
801d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport						    data->supplies);
811d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		else
821d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport			ret = regulator_bulk_disable(data->num_supplies,
831d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport						     data->supplies);
841d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
851d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		if (ret == 0)
861d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport			data->enabled = enabled;
871d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		else
881d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport			dev_err(dev, "Failed to configure state: %d\n", ret);
891d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	}
901d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	mutex_unlock(&data->lock);
911d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
921d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	return count;
931d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport}
941d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
957c314991d7d7ad4edf96e8322bcb30e8452957b7Liam Girdwoodstatic DEVICE_ATTR(name, 0444, reg_show_name, NULL);
967c314991d7d7ad4edf96e8322bcb30e8452957b7Liam Girdwoodstatic DEVICE_ATTR(state, 0644, reg_show_state, reg_set_state);
971d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
98e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbistatic struct attribute *attributes[] = {
99e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi	&dev_attr_name.attr,
100e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi	&dev_attr_state.attr,
101e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi	NULL,
102e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi};
103e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi
104e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbistatic const struct attribute_group attr_group = {
105e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi	.attrs	= attributes,
1061d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport};
1071d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
1081d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoportstatic int regulator_userspace_consumer_probe(struct platform_device *pdev)
1091d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport{
1101d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	struct regulator_userspace_consumer_data *pdata;
1111d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	struct userspace_consumer_data *drvdata;
112e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi	int ret;
1131d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
1141d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	pdata = pdev->dev.platform_data;
1151d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	if (!pdata)
1161d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		return -EINVAL;
1171d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
1181d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	drvdata = kzalloc(sizeof(struct userspace_consumer_data), GFP_KERNEL);
1191d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	if (drvdata == NULL)
1201d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		return -ENOMEM;
1211d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
1221d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	drvdata->name = pdata->name;
1231d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	drvdata->num_supplies = pdata->num_supplies;
1241d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	drvdata->supplies = pdata->supplies;
1251d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
1261d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	mutex_init(&drvdata->lock);
1271d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
1281d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	ret = regulator_bulk_get(&pdev->dev, drvdata->num_supplies,
1291d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport				 drvdata->supplies);
1301d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	if (ret) {
1311d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		dev_err(&pdev->dev, "Failed to get supplies: %d\n", ret);
1321d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		goto err_alloc_supplies;
1331d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	}
1341d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
135e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi	ret = sysfs_create_group(&pdev->dev.kobj, &attr_group);
136e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi	if (ret != 0)
137e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi		goto err_create_attrs;
1381d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
139e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi	if (pdata->init_on) {
1401d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		ret = regulator_bulk_enable(drvdata->num_supplies,
1411d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport					    drvdata->supplies);
142e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi		if (ret) {
143e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi			dev_err(&pdev->dev,
144e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi				"Failed to set initial state: %d\n", ret);
145e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi			goto err_enable;
146e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi		}
1471d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	}
1481d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
149e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi	drvdata->enabled = pdata->init_on;
1501d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	platform_set_drvdata(pdev, drvdata);
1511d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
1521d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	return 0;
1531d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
154e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbierr_enable:
155e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi	sysfs_remove_group(&pdev->dev.kobj, &attr_group);
1561d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
157e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbierr_create_attrs:
1581d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	regulator_bulk_free(drvdata->num_supplies, drvdata->supplies);
1591d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
1601d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoporterr_alloc_supplies:
1611d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	kfree(drvdata);
1621d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	return ret;
1631d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport}
1641d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
1651d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoportstatic int regulator_userspace_consumer_remove(struct platform_device *pdev)
1661d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport{
1671d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	struct userspace_consumer_data *data = platform_get_drvdata(pdev);
1681d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
169e9d62698e8e5228638093c48783eb9dda788f1c3Felipe Balbi	sysfs_remove_group(&pdev->dev.kobj, &attr_group);
1701d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
1711d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	if (data->enabled)
1721d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		regulator_bulk_disable(data->num_supplies, data->supplies);
1731d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
1741d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	regulator_bulk_free(data->num_supplies, data->supplies);
1751d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	kfree(data);
1761d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
1771d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	return 0;
1781d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport}
1791d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
1801d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoportstatic struct platform_driver regulator_userspace_consumer_driver = {
1811d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	.probe		= regulator_userspace_consumer_probe,
1821d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	.remove		= regulator_userspace_consumer_remove,
1831d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	.driver		= {
1841d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport		.name		= "reg-userspace-consumer",
1851d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport	},
1861d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport};
1871d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
188005d610f2abc550172726b997f5cfe683769cc1cAxel Linmodule_platform_driver(regulator_userspace_consumer_driver);
1891d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike Rapoport
1901d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike RapoportMODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>");
1911d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike RapoportMODULE_DESCRIPTION("Userspace consumer for voltage and current regulators");
1921d98cccf7f8b944ba4ea56d14bbb7c2eeee59bfeMike RapoportMODULE_LICENSE("GPL");
193