diff mbox series

[RFC] rtc: abx80x: Add attributes to control oscillator switching modes

Message ID 20200616114150.95673-1-kevin+linux@km6g.us
State New
Headers show
Series [RFC] rtc: abx80x: Add attributes to control oscillator switching modes | expand

Commit Message

Kevin P. Fleming June 16, 2020, 11:41 a.m. UTC
The devices supported by this driver have two oscillator switching
modes available which can switch from the crystal oscillator to
the RC oscillator when triggered by an event.

The 'AOS' mode switches to the RC oscillator when the primary power
supply is lost, and the device is operating on backup power. A boolean
device attribute named 'auto_osc_switch' controls this mode.

The 'FOS' mode switches to the RC oscillator when a failure of the
crystal oscillator has been detected by the device. A boolean device
attribute named 'fail_osc_switch' controls this mode.

Signed-off-by: Kevin P. Fleming <kevin+linux@km6g.us>
---
 drivers/rtc/rtc-abx80x.c | 163 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 151 insertions(+), 12 deletions(-)
diff mbox series

Patch

diff --git a/drivers/rtc/rtc-abx80x.c b/drivers/rtc/rtc-abx80x.c
index 1b428fe2029ef..3a92015395417 100644
--- a/drivers/rtc/rtc-abx80x.c
+++ b/drivers/rtc/rtc-abx80x.c
@@ -55,10 +55,9 @@ 
 
 #define ABX8XX_REG_OSC		0x1c
 #define ABX8XX_OSC_FOS		BIT(3)
-#define ABX8XX_OSC_BOS		BIT(4)
+#define ABX8XX_OSC_AOS		BIT(4)
 #define ABX8XX_OSC_ACAL_512	BIT(5)
 #define ABX8XX_OSC_ACAL_1024	BIT(6)
-
 #define ABX8XX_OSC_OSEL		BIT(7)
 
 #define ABX8XX_REG_OSS		0x1d
@@ -136,15 +135,34 @@  static int abx80x_is_rc_mode(struct i2c_client *client)
 	int flags = 0;
 
 	flags =  i2c_smbus_read_byte_data(client, ABX8XX_REG_OSS);
-	if (flags < 0) {
-		dev_err(&client->dev,
-			"Failed to read autocalibration attribute\n");
+	if (flags < 0)
 		return flags;
-	}
 
 	return (flags & ABX8XX_OSS_OMODE) ? 1 : 0;
 }
 
+static int abx80x_is_auto_osc_switch_mode(struct i2c_client *client)
+{
+	int flags = 0;
+
+	flags =  i2c_smbus_read_byte_data(client, ABX8XX_REG_OSC);
+	if (flags < 0)
+		return flags;
+
+	return (flags & ABX8XX_OSC_AOS) ? 1 : 0;
+}
+
+static int abx80x_is_fail_osc_switch_mode(struct i2c_client *client)
+{
+	int flags = 0;
+
+	flags =  i2c_smbus_read_byte_data(client, ABX8XX_REG_OSC);
+	if (flags < 0)
+		return flags;
+
+	return (flags & ABX8XX_OSC_FOS) ? 1 : 0;
+}
+
 static int abx80x_set_autocal_filter(struct i2c_client *client, u8 filter_cfg)
 {
 	int err;
@@ -196,8 +214,11 @@  static int abx80x_rtc_read_time(struct device *dev, struct rtc_time *tm)
 
 	/* Read the Oscillator Failure only in XT mode */
 	rc_mode = abx80x_is_rc_mode(client);
-	if (rc_mode < 0)
+	if (rc_mode < 0) {
+		dev_err(&client->dev,
+			"Failed to read oscillator mode\n");
 		return rc_mode;
+	}
 
 	if (!rc_mode) {
 		flags = i2c_smbus_read_byte_data(client, ABX8XX_REG_OSS);
@@ -497,7 +518,7 @@  static ssize_t oscillator_show(struct device *dev,
 	rc_mode = abx80x_is_rc_mode(client);
 
 	if (rc_mode < 0) {
-		dev_err(dev, "Failed to read RTC oscillator selection\n");
+		dev_err(dev, "Failed to read oscillator mode\n");
 		sprintf(buf, "\n");
 		return rc_mode;
 	}
@@ -510,14 +531,132 @@  static ssize_t oscillator_show(struct device *dev,
 
 static DEVICE_ATTR_RW(oscillator);
 
-static struct attribute *rtc_calib_attrs[] = {
+static ssize_t auto_osc_switch_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev->parent);
+	int retval, flags;
+	bool auto_osc_switch = false;
+
+	retval = kstrtobool(buf,  &auto_osc_switch);
+	if (retval < 0) {
+		dev_err(dev, "Failed to parse auto_osc_switch attribute\n");
+		return -EINVAL;
+	}
+
+	flags =  i2c_smbus_read_byte_data(client, ABX8XX_REG_OSC);
+	if (flags < 0)
+		return flags;
+
+	if (auto_osc_switch)
+		flags |= (ABX8XX_OSC_AOS);
+	else
+		flags &= ~(ABX8XX_OSC_AOS);
+
+	/* Unlock write access on Oscillator Control register */
+	if (abx80x_write_config_key(client, ABX8XX_CFG_KEY_OSC) < 0)
+		return -EIO;
+
+	retval = i2c_smbus_write_byte_data(client, ABX8XX_REG_OSC, flags);
+	if (retval < 0) {
+		dev_err(dev, "Failed to write Oscillator Control register\n");
+		return retval;
+	}
+
+	return retval ? retval : count;
+}
+
+static ssize_t auto_osc_switch_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	int aos_mode = 0;
+	struct i2c_client *client = to_i2c_client(dev->parent);
+
+	aos_mode = abx80x_is_auto_osc_switch_mode(client);
+
+	if (aos_mode < 0) {
+		dev_err(dev, "Failed to read RTC oscillator control register\n");
+		sprintf(buf, "\n");
+		return aos_mode;
+	}
+
+	if (aos_mode)
+		return sprintf(buf, "on\n");
+	else
+		return sprintf(buf, "off\n");
+}
+
+static DEVICE_ATTR_RW(auto_osc_switch);
+
+static ssize_t fail_osc_switch_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev->parent);
+	int retval, flags;
+	bool fail_osc_switch = false;
+
+	retval = kstrtobool(buf,  &fail_osc_switch);
+	if (retval < 0) {
+		dev_err(dev, "Failed to parse fail_osc_switch attribute\n");
+		return -EINVAL;
+	}
+
+	flags =  i2c_smbus_read_byte_data(client, ABX8XX_REG_OSC);
+	if (flags < 0)
+		return flags;
+
+	if (fail_osc_switch)
+		flags |= (ABX8XX_OSC_FOS);
+	else
+		flags &= ~(ABX8XX_OSC_FOS);
+
+	/* Unlock write access on Oscillator Control register */
+	if (abx80x_write_config_key(client, ABX8XX_CFG_KEY_OSC) < 0)
+		return -EIO;
+
+	retval = i2c_smbus_write_byte_data(client, ABX8XX_REG_OSC, flags);
+	if (retval < 0) {
+		dev_err(dev, "Failed to write Oscillator Control register\n");
+		return retval;
+	}
+
+	return retval ? retval : count;
+}
+
+static ssize_t fail_osc_switch_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	int aos_mode = 0;
+	struct i2c_client *client = to_i2c_client(dev->parent);
+
+	aos_mode = abx80x_is_fail_osc_switch_mode(client);
+
+	if (aos_mode < 0) {
+		dev_err(dev, "Failed to read RTC oscillator control register\n");
+		sprintf(buf, "\n");
+		return aos_mode;
+	}
+
+	if (aos_mode)
+		return sprintf(buf, "on\n");
+	else
+		return sprintf(buf, "off\n");
+}
+
+static DEVICE_ATTR_RW(fail_osc_switch);
+
+static struct attribute *rtc_osc_ctrl_attrs[] = {
 	&dev_attr_autocalibration.attr,
 	&dev_attr_oscillator.attr,
+	&dev_attr_auto_osc_switch.attr,
+	&dev_attr_fail_osc_switch.attr,
 	NULL,
 };
 
-static const struct attribute_group rtc_calib_attr_group = {
-	.attrs		= rtc_calib_attrs,
+static const struct attribute_group rtc_osc_ctrl_attr_group = {
+	.attrs		= rtc_osc_ctrl_attrs,
 };
 
 static int abx80x_alarm_irq_enable(struct device *dev, unsigned int enabled)
@@ -871,7 +1010,7 @@  static int abx80x_probe(struct i2c_client *client,
 		}
 	}
 
-	err = rtc_add_group(priv->rtc, &rtc_calib_attr_group);
+	err = rtc_add_group(priv->rtc, &rtc_osc_ctrl_attr_group);
 	if (err) {
 		dev_err(&client->dev, "Failed to create sysfs group: %d\n",
 			err);