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