brintos

brintos / linux-shallow public Read only

0
0
Text · 6.2 KiB · 0e8ab15 Raw
261 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * CZ.NIC's Turris Omnia MCU system off and RTC wakeup driver4 *5 * This is not a true RTC driver (in the sense that it does not provide a6 * real-time clock), rather the MCU implements a wakeup from powered off state7 * at a specified time relative to MCU boot, and we expose this feature via RTC8 * alarm, so that it can be used via the rtcwake command, which is the standard9 * Linux command for this.10 *11 * 2024 by Marek Behún <kabel@kernel.org>12 */13 14#include <linux/crc32.h>15#include <linux/delay.h>16#include <linux/device.h>17#include <linux/err.h>18#include <linux/i2c.h>19#include <linux/kstrtox.h>20#include <linux/reboot.h>21#include <linux/rtc.h>22#include <linux/sysfs.h>23#include <linux/types.h>24 25#include <linux/turris-omnia-mcu-interface.h>26#include "turris-omnia-mcu.h"27 28static int omnia_get_uptime_wakeup(const struct i2c_client *client, u32 *uptime,29				   u32 *wakeup)30{31	__le32 reply[2];32	int err;33 34	err = omnia_cmd_read(client, OMNIA_CMD_GET_UPTIME_AND_WAKEUP, reply,35			     sizeof(reply));36	if (err)37		return err;38 39	if (uptime)40		*uptime = le32_to_cpu(reply[0]);41 42	if (wakeup)43		*wakeup = le32_to_cpu(reply[1]);44 45	return 0;46}47 48static int omnia_read_time(struct device *dev, struct rtc_time *tm)49{50	u32 uptime;51	int err;52 53	err = omnia_get_uptime_wakeup(to_i2c_client(dev), &uptime, NULL);54	if (err)55		return err;56 57	rtc_time64_to_tm(uptime, tm);58 59	return 0;60}61 62static int omnia_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)63{64	struct i2c_client *client = to_i2c_client(dev);65	struct omnia_mcu *mcu = i2c_get_clientdata(client);66	u32 wakeup;67	int err;68 69	err = omnia_get_uptime_wakeup(client, NULL, &wakeup);70	if (err)71		return err;72 73	alrm->enabled = !!wakeup;74	rtc_time64_to_tm(wakeup ?: mcu->rtc_alarm, &alrm->time);75 76	return 0;77}78 79static int omnia_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)80{81	struct i2c_client *client = to_i2c_client(dev);82	struct omnia_mcu *mcu = i2c_get_clientdata(client);83 84	mcu->rtc_alarm = rtc_tm_to_time64(&alrm->time);85 86	if (alrm->enabled)87		return omnia_cmd_write_u32(client, OMNIA_CMD_SET_WAKEUP,88					   mcu->rtc_alarm);89 90	return 0;91}92 93static int omnia_alarm_irq_enable(struct device *dev, unsigned int enabled)94{95	struct i2c_client *client = to_i2c_client(dev);96	struct omnia_mcu *mcu = i2c_get_clientdata(client);97 98	return omnia_cmd_write_u32(client, OMNIA_CMD_SET_WAKEUP,99				   enabled ? mcu->rtc_alarm : 0);100}101 102static const struct rtc_class_ops omnia_rtc_ops = {103	.read_time		= omnia_read_time,104	.read_alarm		= omnia_read_alarm,105	.set_alarm		= omnia_set_alarm,106	.alarm_irq_enable	= omnia_alarm_irq_enable,107};108 109static int omnia_power_off(struct sys_off_data *data)110{111	struct omnia_mcu *mcu = data->cb_data;112	__be32 tmp;113	u8 cmd[9];114	u16 arg;115	int err;116 117	if (mcu->front_button_poweron)118		arg = OMNIA_CMD_POWER_OFF_POWERON_BUTTON;119	else120		arg = 0;121 122	cmd[0] = OMNIA_CMD_POWER_OFF;123	put_unaligned_le16(OMNIA_CMD_POWER_OFF_MAGIC, &cmd[1]);124	put_unaligned_le16(arg, &cmd[3]);125 126	/*127	 * Although all values from and to MCU are passed in little-endian, the128	 * MCU's CRC unit uses big-endian CRC32 polynomial (0x04c11db7), so we129	 * need to use crc32_be() here.130	 */131	tmp = cpu_to_be32(get_unaligned_le32(&cmd[1]));132	put_unaligned_le32(crc32_be(~0, (void *)&tmp, sizeof(tmp)), &cmd[5]);133 134	err = omnia_cmd_write(mcu->client, cmd, sizeof(cmd));135	if (err)136		dev_err(&mcu->client->dev,137			"Unable to send the poweroff command: %d\n", err);138 139	return NOTIFY_DONE;140}141 142static int omnia_restart(struct sys_off_data *data)143{144	struct omnia_mcu *mcu = data->cb_data;145	u8 cmd[3];146	int err;147 148	cmd[0] = OMNIA_CMD_GENERAL_CONTROL;149 150	if (reboot_mode == REBOOT_HARD)151		cmd[1] = cmd[2] = OMNIA_CTL_HARD_RST;152	else153		cmd[1] = cmd[2] = OMNIA_CTL_LIGHT_RST;154 155	err = omnia_cmd_write(mcu->client, cmd, sizeof(cmd));156	if (err)157		dev_err(&mcu->client->dev,158			"Unable to send the restart command: %d\n", err);159 160	/*161	 * MCU needs a little bit to process the I2C command, otherwise it will162	 * do a light reset based on SOC SYSRES_OUT pin.163	 */164	mdelay(1);165 166	return NOTIFY_DONE;167}168 169static ssize_t front_button_poweron_show(struct device *dev,170					 struct device_attribute *a, char *buf)171{172	struct omnia_mcu *mcu = dev_get_drvdata(dev);173 174	return sysfs_emit(buf, "%d\n", mcu->front_button_poweron);175}176 177static ssize_t front_button_poweron_store(struct device *dev,178					  struct device_attribute *a,179					  const char *buf, size_t count)180{181	struct omnia_mcu *mcu = dev_get_drvdata(dev);182	bool val;183	int err;184 185	err = kstrtobool(buf, &val);186	if (err)187		return err;188 189	mcu->front_button_poweron = val;190 191	return count;192}193static DEVICE_ATTR_RW(front_button_poweron);194 195static struct attribute *omnia_mcu_poweroff_attrs[] = {196	&dev_attr_front_button_poweron.attr,197	NULL198};199 200static umode_t poweroff_attrs_visible(struct kobject *kobj, struct attribute *a,201				      int n)202{203	struct device *dev = kobj_to_dev(kobj);204	struct omnia_mcu *mcu = dev_get_drvdata(dev);205 206	if (mcu->features & OMNIA_FEAT_POWEROFF_WAKEUP)207		return a->mode;208 209	return 0;210}211 212const struct attribute_group omnia_mcu_poweroff_group = {213	.attrs = omnia_mcu_poweroff_attrs,214	.is_visible = poweroff_attrs_visible,215};216 217int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu)218{219	struct device *dev = &mcu->client->dev;220	int err;221 222	/* MCU restart is always available */223	err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART,224					    SYS_OFF_PRIO_FIRMWARE,225					    omnia_restart, mcu);226	if (err)227		return dev_err_probe(dev, err,228				     "Cannot register system restart handler\n");229 230	/*231	 * Poweroff and wakeup are available only if POWEROFF_WAKEUP feature is232	 * present.233	 */234	if (!(mcu->features & OMNIA_FEAT_POWEROFF_WAKEUP))235		return 0;236 237	err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF,238					    SYS_OFF_PRIO_FIRMWARE,239					    omnia_power_off, mcu);240	if (err)241		return dev_err_probe(dev, err,242				     "Cannot register system power off handler\n");243 244	mcu->rtcdev = devm_rtc_allocate_device(dev);245	if (IS_ERR(mcu->rtcdev))246		return dev_err_probe(dev, PTR_ERR(mcu->rtcdev),247				     "Cannot allocate RTC device\n");248 249	mcu->rtcdev->ops = &omnia_rtc_ops;250	mcu->rtcdev->range_max = U32_MAX;251	set_bit(RTC_FEATURE_ALARM_WAKEUP_ONLY, mcu->rtcdev->features);252 253	err = devm_rtc_register_device(mcu->rtcdev);254	if (err)255		return dev_err_probe(dev, err, "Cannot register RTC device\n");256 257	mcu->front_button_poweron = true;258 259	return 0;260}261