xref: /openbmc/linux/drivers/hwmon/raspberrypi-hwmon.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
174d1e007SStefan Wahren // SPDX-License-Identifier: GPL-2.0+
274d1e007SStefan Wahren /*
374d1e007SStefan Wahren  * Raspberry Pi voltage sensor driver
474d1e007SStefan Wahren  *
574d1e007SStefan Wahren  * Based on firmware/raspberrypi.c by Noralf Trønnes
674d1e007SStefan Wahren  *
774d1e007SStefan Wahren  * Copyright (C) 2018 Stefan Wahren <stefan.wahren@i2se.com>
874d1e007SStefan Wahren  */
974d1e007SStefan Wahren #include <linux/device.h>
1096dc2b31SMatti Vaittinen #include <linux/devm-helpers.h>
1174d1e007SStefan Wahren #include <linux/err.h>
1274d1e007SStefan Wahren #include <linux/hwmon.h>
1374d1e007SStefan Wahren #include <linux/module.h>
1474d1e007SStefan Wahren #include <linux/platform_device.h>
1574d1e007SStefan Wahren #include <linux/slab.h>
1674d1e007SStefan Wahren #include <linux/workqueue.h>
1774d1e007SStefan Wahren #include <soc/bcm2835/raspberrypi-firmware.h>
1874d1e007SStefan Wahren 
1974d1e007SStefan Wahren #define UNDERVOLTAGE_STICKY_BIT	BIT(16)
2074d1e007SStefan Wahren 
2174d1e007SStefan Wahren struct rpi_hwmon_data {
2274d1e007SStefan Wahren 	struct device *hwmon_dev;
2374d1e007SStefan Wahren 	struct rpi_firmware *fw;
2474d1e007SStefan Wahren 	u32 last_throttled;
2574d1e007SStefan Wahren 	struct delayed_work get_values_poll_work;
2674d1e007SStefan Wahren };
2774d1e007SStefan Wahren 
rpi_firmware_get_throttled(struct rpi_hwmon_data * data)2874d1e007SStefan Wahren static void rpi_firmware_get_throttled(struct rpi_hwmon_data *data)
2974d1e007SStefan Wahren {
3074d1e007SStefan Wahren 	u32 new_uv, old_uv, value;
3174d1e007SStefan Wahren 	int ret;
3274d1e007SStefan Wahren 
3374d1e007SStefan Wahren 	/* Request firmware to clear sticky bits */
3474d1e007SStefan Wahren 	value = 0xffff;
3574d1e007SStefan Wahren 
3674d1e007SStefan Wahren 	ret = rpi_firmware_property(data->fw, RPI_FIRMWARE_GET_THROTTLED,
3774d1e007SStefan Wahren 				    &value, sizeof(value));
3874d1e007SStefan Wahren 	if (ret) {
3974d1e007SStefan Wahren 		dev_err_once(data->hwmon_dev, "Failed to get throttled (%d)\n",
4074d1e007SStefan Wahren 			     ret);
4174d1e007SStefan Wahren 		return;
4274d1e007SStefan Wahren 	}
4374d1e007SStefan Wahren 
4474d1e007SStefan Wahren 	new_uv = value & UNDERVOLTAGE_STICKY_BIT;
4574d1e007SStefan Wahren 	old_uv = data->last_throttled & UNDERVOLTAGE_STICKY_BIT;
4674d1e007SStefan Wahren 	data->last_throttled = value;
4774d1e007SStefan Wahren 
4874d1e007SStefan Wahren 	if (new_uv == old_uv)
4974d1e007SStefan Wahren 		return;
5074d1e007SStefan Wahren 
5174d1e007SStefan Wahren 	if (new_uv)
5274d1e007SStefan Wahren 		dev_crit(data->hwmon_dev, "Undervoltage detected!\n");
5374d1e007SStefan Wahren 	else
5474d1e007SStefan Wahren 		dev_info(data->hwmon_dev, "Voltage normalised\n");
5574d1e007SStefan Wahren 
56d73287eeSArmin Wolf 	hwmon_notify_event(data->hwmon_dev, hwmon_in, hwmon_in_lcrit_alarm, 0);
5774d1e007SStefan Wahren }
5874d1e007SStefan Wahren 
get_values_poll(struct work_struct * work)5974d1e007SStefan Wahren static void get_values_poll(struct work_struct *work)
6074d1e007SStefan Wahren {
6174d1e007SStefan Wahren 	struct rpi_hwmon_data *data;
6274d1e007SStefan Wahren 
6374d1e007SStefan Wahren 	data = container_of(work, struct rpi_hwmon_data,
6474d1e007SStefan Wahren 			    get_values_poll_work.work);
6574d1e007SStefan Wahren 
6674d1e007SStefan Wahren 	rpi_firmware_get_throttled(data);
6774d1e007SStefan Wahren 
6874d1e007SStefan Wahren 	/*
6974d1e007SStefan Wahren 	 * We can't run faster than the sticky shift (100ms) since we get
7074d1e007SStefan Wahren 	 * flipping in the sticky bits that are cleared.
7174d1e007SStefan Wahren 	 */
7274d1e007SStefan Wahren 	schedule_delayed_work(&data->get_values_poll_work, 2 * HZ);
7374d1e007SStefan Wahren }
7474d1e007SStefan Wahren 
rpi_read(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long * val)7574d1e007SStefan Wahren static int rpi_read(struct device *dev, enum hwmon_sensor_types type,
7674d1e007SStefan Wahren 		    u32 attr, int channel, long *val)
7774d1e007SStefan Wahren {
7874d1e007SStefan Wahren 	struct rpi_hwmon_data *data = dev_get_drvdata(dev);
7974d1e007SStefan Wahren 
8074d1e007SStefan Wahren 	*val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT);
8174d1e007SStefan Wahren 	return 0;
8274d1e007SStefan Wahren }
8374d1e007SStefan Wahren 
rpi_is_visible(const void * _data,enum hwmon_sensor_types type,u32 attr,int channel)8474d1e007SStefan Wahren static umode_t rpi_is_visible(const void *_data, enum hwmon_sensor_types type,
8574d1e007SStefan Wahren 			      u32 attr, int channel)
8674d1e007SStefan Wahren {
8774d1e007SStefan Wahren 	return 0444;
8874d1e007SStefan Wahren }
8974d1e007SStefan Wahren 
90*70df9a55SKrzysztof Kozlowski static const struct hwmon_channel_info * const rpi_info[] = {
910c42186eSGuenter Roeck 	HWMON_CHANNEL_INFO(in,
920c42186eSGuenter Roeck 			   HWMON_I_LCRIT_ALARM),
9374d1e007SStefan Wahren 	NULL
9474d1e007SStefan Wahren };
9574d1e007SStefan Wahren 
9674d1e007SStefan Wahren static const struct hwmon_ops rpi_hwmon_ops = {
9774d1e007SStefan Wahren 	.is_visible = rpi_is_visible,
9874d1e007SStefan Wahren 	.read = rpi_read,
9974d1e007SStefan Wahren };
10074d1e007SStefan Wahren 
10174d1e007SStefan Wahren static const struct hwmon_chip_info rpi_chip_info = {
10274d1e007SStefan Wahren 	.ops = &rpi_hwmon_ops,
10374d1e007SStefan Wahren 	.info = rpi_info,
10474d1e007SStefan Wahren };
10574d1e007SStefan Wahren 
rpi_hwmon_probe(struct platform_device * pdev)10674d1e007SStefan Wahren static int rpi_hwmon_probe(struct platform_device *pdev)
10774d1e007SStefan Wahren {
10874d1e007SStefan Wahren 	struct device *dev = &pdev->dev;
10974d1e007SStefan Wahren 	struct rpi_hwmon_data *data;
11096dc2b31SMatti Vaittinen 	int ret;
11174d1e007SStefan Wahren 
11274d1e007SStefan Wahren 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
11374d1e007SStefan Wahren 	if (!data)
11474d1e007SStefan Wahren 		return -ENOMEM;
11574d1e007SStefan Wahren 
11674d1e007SStefan Wahren 	/* Parent driver assure that firmware is correct */
11774d1e007SStefan Wahren 	data->fw = dev_get_drvdata(dev->parent);
11874d1e007SStefan Wahren 
11974d1e007SStefan Wahren 	data->hwmon_dev = devm_hwmon_device_register_with_info(dev, "rpi_volt",
12074d1e007SStefan Wahren 							       data,
12174d1e007SStefan Wahren 							       &rpi_chip_info,
12274d1e007SStefan Wahren 							       NULL);
1239c6d5551SArmin Wolf 	if (IS_ERR(data->hwmon_dev))
1249c6d5551SArmin Wolf 		return PTR_ERR(data->hwmon_dev);
12574d1e007SStefan Wahren 
12696dc2b31SMatti Vaittinen 	ret = devm_delayed_work_autocancel(dev, &data->get_values_poll_work,
12796dc2b31SMatti Vaittinen 					   get_values_poll);
12896dc2b31SMatti Vaittinen 	if (ret)
12996dc2b31SMatti Vaittinen 		return ret;
13074d1e007SStefan Wahren 	platform_set_drvdata(pdev, data);
13174d1e007SStefan Wahren 
13274d1e007SStefan Wahren 	schedule_delayed_work(&data->get_values_poll_work, 2 * HZ);
13374d1e007SStefan Wahren 
1349c6d5551SArmin Wolf 	return 0;
13574d1e007SStefan Wahren }
13674d1e007SStefan Wahren 
13774d1e007SStefan Wahren static struct platform_driver rpi_hwmon_driver = {
13874d1e007SStefan Wahren 	.probe = rpi_hwmon_probe,
13974d1e007SStefan Wahren 	.driver = {
14074d1e007SStefan Wahren 		.name = "raspberrypi-hwmon",
14174d1e007SStefan Wahren 	},
14274d1e007SStefan Wahren };
14374d1e007SStefan Wahren module_platform_driver(rpi_hwmon_driver);
14474d1e007SStefan Wahren 
14510b1f2a9SStefan Wahren MODULE_AUTHOR("Stefan Wahren <wahrenst@gmx.net>");
14674d1e007SStefan Wahren MODULE_DESCRIPTION("Raspberry Pi voltage sensor driver");
14774d1e007SStefan Wahren MODULE_LICENSE("GPL v2");
148d07f05fbSPeter Robinson MODULE_ALIAS("platform:raspberrypi-hwmon");
149