|
@@ -49,7 +49,19 @@
|
|
|
|
|
|
#define ABX8XX_REG_CD_TIMER_CTL 0x18
|
|
|
|
|
|
+#define ABX8XX_REG_OSC 0x1c
|
|
|
+#define ABX8XX_OSC_FOS BIT(3)
|
|
|
+#define ABX8XX_OSC_BOS 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
|
|
|
+#define ABX8XX_OSS_OMODE BIT(4)
|
|
|
+
|
|
|
#define ABX8XX_REG_CFG_KEY 0x1f
|
|
|
+#define ABX8XX_CFG_KEY_OSC 0xa1
|
|
|
#define ABX8XX_CFG_KEY_MISC 0x9d
|
|
|
|
|
|
#define ABX8XX_REG_ID0 0x28
|
|
@@ -81,6 +93,20 @@ static struct abx80x_cap abx80x_caps[] = {
|
|
|
[ABX80X] = {.pn = 0}
|
|
|
};
|
|
|
|
|
|
+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");
|
|
|
+ return flags;
|
|
|
+ }
|
|
|
+
|
|
|
+ return (flags & ABX8XX_OSS_OMODE) ? 1 : 0;
|
|
|
+}
|
|
|
+
|
|
|
static int abx80x_enable_trickle_charger(struct i2c_client *client,
|
|
|
u8 trickle_cfg)
|
|
|
{
|
|
@@ -248,6 +274,174 @@ static int abx80x_set_alarm(struct device *dev, struct rtc_wkalrm *t)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static int abx80x_rtc_set_autocalibration(struct device *dev,
|
|
|
+ int autocalibration)
|
|
|
+{
|
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
|
+ int retval, flags = 0;
|
|
|
+
|
|
|
+ if ((autocalibration != 0) && (autocalibration != 1024) &&
|
|
|
+ (autocalibration != 512)) {
|
|
|
+ dev_err(dev, "autocalibration value outside permitted range\n");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ flags = i2c_smbus_read_byte_data(client, ABX8XX_REG_OSC);
|
|
|
+ if (flags < 0)
|
|
|
+ return flags;
|
|
|
+
|
|
|
+ if (autocalibration == 0) {
|
|
|
+ flags &= ~(ABX8XX_OSC_ACAL_512 | ABX8XX_OSC_ACAL_1024);
|
|
|
+ } else if (autocalibration == 1024) {
|
|
|
+ /* 1024 autocalibration is 0x10 */
|
|
|
+ flags |= ABX8XX_OSC_ACAL_1024;
|
|
|
+ flags &= ~(ABX8XX_OSC_ACAL_512);
|
|
|
+ } else {
|
|
|
+ /* 512 autocalibration is 0x11 */
|
|
|
+ flags |= (ABX8XX_OSC_ACAL_1024 | ABX8XX_OSC_ACAL_512);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Unlock write access to Oscillator Control Register */
|
|
|
+ retval = i2c_smbus_write_byte_data(client, ABX8XX_REG_CFG_KEY,
|
|
|
+ ABX8XX_CFG_KEY_OSC);
|
|
|
+ if (retval < 0) {
|
|
|
+ dev_err(dev, "Failed to write CONFIG_KEY register\n");
|
|
|
+ return retval;
|
|
|
+ }
|
|
|
+
|
|
|
+ retval = i2c_smbus_write_byte_data(client, ABX8XX_REG_OSC, flags);
|
|
|
+
|
|
|
+ return retval;
|
|
|
+}
|
|
|
+
|
|
|
+static int abx80x_rtc_get_autocalibration(struct device *dev)
|
|
|
+{
|
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
|
+ int flags = 0, autocalibration;
|
|
|
+
|
|
|
+ flags = i2c_smbus_read_byte_data(client, ABX8XX_REG_OSC);
|
|
|
+ if (flags < 0)
|
|
|
+ return flags;
|
|
|
+
|
|
|
+ if (flags & ABX8XX_OSC_ACAL_512)
|
|
|
+ autocalibration = 512;
|
|
|
+ else if (flags & ABX8XX_OSC_ACAL_1024)
|
|
|
+ autocalibration = 1024;
|
|
|
+ else
|
|
|
+ autocalibration = 0;
|
|
|
+
|
|
|
+ return autocalibration;
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t autocalibration_store(struct device *dev,
|
|
|
+ struct device_attribute *attr,
|
|
|
+ const char *buf, size_t count)
|
|
|
+{
|
|
|
+ int retval;
|
|
|
+ unsigned long autocalibration = 0;
|
|
|
+
|
|
|
+ retval = kstrtoul(buf, 10, &autocalibration);
|
|
|
+ if (retval < 0) {
|
|
|
+ dev_err(dev, "Failed to store RTC autocalibration attribute\n");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ retval = abx80x_rtc_set_autocalibration(dev, autocalibration);
|
|
|
+
|
|
|
+ return retval ? retval : count;
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t autocalibration_show(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ int autocalibration = 0;
|
|
|
+
|
|
|
+ autocalibration = abx80x_rtc_get_autocalibration(dev);
|
|
|
+ if (autocalibration < 0) {
|
|
|
+ dev_err(dev, "Failed to read RTC autocalibration\n");
|
|
|
+ sprintf(buf, "0\n");
|
|
|
+ return autocalibration;
|
|
|
+ }
|
|
|
+
|
|
|
+ return sprintf(buf, "%d\n", autocalibration);
|
|
|
+}
|
|
|
+
|
|
|
+static DEVICE_ATTR_RW(autocalibration);
|
|
|
+
|
|
|
+static ssize_t oscillator_store(struct device *dev,
|
|
|
+ struct device_attribute *attr,
|
|
|
+ const char *buf, size_t count)
|
|
|
+{
|
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
|
+ int retval, flags, rc_mode = 0;
|
|
|
+
|
|
|
+ if (strncmp(buf, "rc", 2) == 0) {
|
|
|
+ rc_mode = 1;
|
|
|
+ } else if (strncmp(buf, "xtal", 4) == 0) {
|
|
|
+ rc_mode = 0;
|
|
|
+ } else {
|
|
|
+ dev_err(dev, "Oscillator selection value outside permitted ones\n");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ flags = i2c_smbus_read_byte_data(client, ABX8XX_REG_OSC);
|
|
|
+ if (flags < 0)
|
|
|
+ return flags;
|
|
|
+
|
|
|
+ if (rc_mode == 0)
|
|
|
+ flags &= ~(ABX8XX_OSC_OSEL);
|
|
|
+ else
|
|
|
+ flags |= (ABX8XX_OSC_OSEL);
|
|
|
+
|
|
|
+ /* Unlock write access on Oscillator Control register */
|
|
|
+ retval = i2c_smbus_write_byte_data(client, ABX8XX_REG_CFG_KEY,
|
|
|
+ ABX8XX_CFG_KEY_OSC);
|
|
|
+ if (retval < 0) {
|
|
|
+ dev_err(dev, "Failed to write CONFIG_KEY register\n");
|
|
|
+ return retval;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 oscillator_show(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ int rc_mode = 0;
|
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
|
+
|
|
|
+ rc_mode = abx80x_is_rc_mode(client);
|
|
|
+
|
|
|
+ if (rc_mode < 0) {
|
|
|
+ dev_err(dev, "Failed to read RTC oscillator selection\n");
|
|
|
+ sprintf(buf, "\n");
|
|
|
+ return rc_mode;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (rc_mode)
|
|
|
+ return sprintf(buf, "rc\n");
|
|
|
+ else
|
|
|
+ return sprintf(buf, "xtal\n");
|
|
|
+}
|
|
|
+
|
|
|
+static DEVICE_ATTR_RW(oscillator);
|
|
|
+
|
|
|
+static struct attribute *rtc_calib_attrs[] = {
|
|
|
+ &dev_attr_autocalibration.attr,
|
|
|
+ &dev_attr_oscillator.attr,
|
|
|
+ NULL,
|
|
|
+};
|
|
|
+
|
|
|
+static const struct attribute_group rtc_calib_attr_group = {
|
|
|
+ .attrs = rtc_calib_attrs,
|
|
|
+};
|
|
|
+
|
|
|
static int abx80x_alarm_irq_enable(struct device *dev, unsigned int enabled)
|
|
|
{
|
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
@@ -303,6 +497,13 @@ static int abx80x_dt_trickle_cfg(struct device_node *np)
|
|
|
return (trickle_cfg | i);
|
|
|
}
|
|
|
|
|
|
+static void rtc_calib_remove_sysfs_group(void *_dev)
|
|
|
+{
|
|
|
+ struct device *dev = _dev;
|
|
|
+
|
|
|
+ sysfs_remove_group(&dev->kobj, &rtc_calib_attr_group);
|
|
|
+}
|
|
|
+
|
|
|
static int abx80x_probe(struct i2c_client *client,
|
|
|
const struct i2c_device_id *id)
|
|
|
{
|
|
@@ -405,6 +606,24 @@ static int abx80x_probe(struct i2c_client *client,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /* Export sysfs entries */
|
|
|
+ err = sysfs_create_group(&(&client->dev)->kobj, &rtc_calib_attr_group);
|
|
|
+ if (err) {
|
|
|
+ dev_err(&client->dev, "Failed to create sysfs group: %d\n",
|
|
|
+ err);
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+
|
|
|
+ err = devm_add_action(&client->dev, rtc_calib_remove_sysfs_group,
|
|
|
+ &client->dev);
|
|
|
+ if (err) {
|
|
|
+ rtc_calib_remove_sysfs_group(&client->dev);
|
|
|
+ dev_err(&client->dev,
|
|
|
+ "Failed to add sysfs cleanup action: %d\n",
|
|
|
+ err);
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|