2017-04-25 14:45:35

by Gary Bisson

[permalink] [raw]
Subject: [PATCH 0/4] rtc: m41t80: improve SQW support

Hi,

This series aims at improving the square-wave support for some of the
devices supported by the m41t80 driver.

The first two patches are fixes that prevent the driver from overriding
the SQW configuration (divider & enable bit).

Then the last two remove the old sysfs entry and replace it with a clock
provider approach.

It has been tested on a Boundary Devices Nitrogen6_MAX which features a
MicroCrystal RV4162 RTC. The SQW (named clkout in RV4162 datasheet) can be
probed on TP70 on the platform.

Let me know if you have any question/suggestion.

Regards,
Gary

Gary Bisson (4):
rtc: m41t80: fix SQWE override when setting an alarm
rtc: m41t80: fix SQW dividers override when setting a date
rtc: m41t80: remove sqw sysfs entry
rtc: m41t80: add clock provider support

drivers/rtc/rtc-m41t80.c | 251 +++++++++++++++++++++++++++++++++--------------
1 file changed, 175 insertions(+), 76 deletions(-)

--
2.11.0


2017-04-25 14:45:45

by Gary Bisson

[permalink] [raw]
Subject: [PATCH 1/4] rtc: m41t80: fix SQWE override when setting an alarm

Currently setting an alarm clears the SQWE bit which means that the
clock output is disabled no matter its previous state.

Signed-off-by: Gary Bisson <[email protected]>
---
drivers/rtc/rtc-m41t80.c | 3 +++
1 file changed, 3 insertions(+)

diff --git a/drivers/rtc/rtc-m41t80.c b/drivers/rtc/rtc-m41t80.c
index 58698d21c2c3..93684ab293f2 100644
--- a/drivers/rtc/rtc-m41t80.c
+++ b/drivers/rtc/rtc-m41t80.c
@@ -273,6 +273,9 @@ static int m41t80_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
return err;
}

+ /* Keep SQWE bit value */
+ alarmvals[0] |= (ret & M41T80_ALMON_SQWE);
+
ret = i2c_smbus_read_byte_data(client, M41T80_REG_FLAGS);
if (ret < 0)
return ret;
--
2.11.0

2017-04-25 14:45:57

by Gary Bisson

[permalink] [raw]
Subject: [PATCH 2/4] rtc: m41t80: fix SQW dividers override when setting a date

This patch is only relevant for RTC with the SQ_ALT feature which
means the clock output frequency divider is stored in the weekday
register.

Current implementation discards the previous dividers value and clear
them as soon as the time is set.

Signed-off-by: Gary Bisson <[email protected]>
---
drivers/rtc/rtc-m41t80.c | 12 ++++++++++++
1 file changed, 12 insertions(+)

diff --git a/drivers/rtc/rtc-m41t80.c b/drivers/rtc/rtc-m41t80.c
index 93684ab293f2..0c142653c309 100644
--- a/drivers/rtc/rtc-m41t80.c
+++ b/drivers/rtc/rtc-m41t80.c
@@ -168,6 +168,7 @@ static int m41t80_get_datetime(struct i2c_client *client,
/* Sets the given date and time to the real time clock. */
static int m41t80_set_datetime(struct i2c_client *client, struct rtc_time *tm)
{
+ struct m41t80_data *clientdata = i2c_get_clientdata(client);
unsigned char buf[8];
int err, flags;

@@ -183,6 +184,17 @@ static int m41t80_set_datetime(struct i2c_client *client, struct rtc_time *tm)
buf[M41T80_REG_YEAR] = bin2bcd(tm->tm_year - 100);
buf[M41T80_REG_WDAY] = tm->tm_wday;

+ /* If the square wave output is controlled in the weekday register */
+ if (clientdata->features & M41T80_FEATURE_SQ_ALT) {
+ int val;
+
+ val = i2c_smbus_read_byte_data(client, M41T80_REG_WDAY);
+ if (val < 0)
+ return val;
+
+ buf[M41T80_REG_WDAY] |= (val & 0xf0);
+ }
+
err = i2c_smbus_write_i2c_block_data(client, M41T80_REG_SSEC,
sizeof(buf), buf);
if (err < 0) {
--
2.11.0

2017-04-25 14:46:12

by Gary Bisson

[permalink] [raw]
Subject: [PATCH 4/4] rtc: m41t80: add clock provider support

Some devices supported by the m41t80 driver have a programmable
square-wave output signal (see M41T80_FEATURE_SQ).

This enables to use this feature as a clock provider of common
clock framework.

Signed-off-by: Gary Bisson <[email protected]>
---
drivers/rtc/rtc-m41t80.c | 172 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 172 insertions(+)

diff --git a/drivers/rtc/rtc-m41t80.c b/drivers/rtc/rtc-m41t80.c
index 2bdf494a9bd4..ea3d1efe9192 100644
--- a/drivers/rtc/rtc-m41t80.c
+++ b/drivers/rtc/rtc-m41t80.c
@@ -16,6 +16,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/bcd.h>
+#include <linux/clk-provider.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
@@ -52,6 +53,8 @@
#define M41T80_ALARM_REG_SIZE \
(M41T80_REG_ALARM_SEC + 1 - M41T80_REG_ALARM_MON)

+#define M41T80_SQW_MAX_FREQ 32768
+
#define M41T80_SEC_ST BIT(7) /* ST: Stop Bit */
#define M41T80_ALMON_AFE BIT(7) /* AFE: AF Enable Bit */
#define M41T80_ALMON_SQWE BIT(6) /* SQWE: SQW Enable Bit */
@@ -88,7 +91,11 @@ MODULE_DEVICE_TABLE(i2c, m41t80_id);

struct m41t80_data {
u8 features;
+ struct i2c_client *client;
struct rtc_device *rtc;
+#ifdef CONFIG_COMMON_CLK
+ struct clk_hw sqw;
+#endif
};

static irqreturn_t m41t80_handle_irq(int irq, void *dev_id)
@@ -396,6 +403,166 @@ static struct attribute_group attr_group = {
.attrs = attrs,
};

+#ifdef CONFIG_COMMON_CLK
+#define sqw_to_m41t80_data(_hw) container_of(_hw, struct m41t80_data, sqw)
+
+static unsigned long m41t80_sqw_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct m41t80_data *m41t80 = sqw_to_m41t80_data(hw);
+ struct i2c_client *client = m41t80->client;
+ int reg_sqw = (m41t80->features & M41T80_FEATURE_SQ_ALT) ?
+ M41T80_REG_WDAY : M41T80_REG_SQW;
+ int ret = i2c_smbus_read_byte_data(client, reg_sqw);
+ unsigned long val = M41T80_SQW_MAX_FREQ;
+
+ if (ret < 0)
+ return 0;
+
+ ret >>= 4;
+ if (ret == 0)
+ val = 0;
+ else if (ret > 1)
+ val = val / (1 << ret);
+
+ return val;
+}
+
+static long m41t80_sqw_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ int i, freq = M41T80_SQW_MAX_FREQ;
+
+ if (freq <= rate)
+ return freq;
+
+ for (i = 2; i <= ilog2(M41T80_SQW_MAX_FREQ); i++) {
+ freq /= 1 << i;
+ if (freq <= rate)
+ return freq;
+ }
+
+ return 0;
+}
+
+static int m41t80_sqw_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct m41t80_data *m41t80 = sqw_to_m41t80_data(hw);
+ struct i2c_client *client = m41t80->client;
+ int reg_sqw = (m41t80->features & M41T80_FEATURE_SQ_ALT) ?
+ M41T80_REG_WDAY : M41T80_REG_SQW;
+ int reg, ret, val = 0;
+
+ if (rate) {
+ if (!is_power_of_2(rate))
+ return -EINVAL;
+ val = ilog2(rate);
+ if (val == ilog2(M41T80_SQW_MAX_FREQ))
+ val = 1;
+ else if (val < (ilog2(M41T80_SQW_MAX_FREQ) - 1))
+ val = ilog2(M41T80_SQW_MAX_FREQ) - val;
+ else
+ return -EINVAL;
+ }
+
+ reg = i2c_smbus_read_byte_data(client, reg_sqw);
+ if (reg < 0)
+ return reg;
+
+ reg = (reg & 0x0f) | (val << 4);
+
+ ret = i2c_smbus_write_byte_data(client, reg_sqw, reg);
+ if (ret < 0)
+ return ret;
+
+ return -EINVAL;
+}
+
+static int m41t80_sqw_control(struct clk_hw *hw, bool enable)
+{
+ struct m41t80_data *m41t80 = sqw_to_m41t80_data(hw);
+ struct i2c_client *client = m41t80->client;
+ int ret = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_MON);
+
+ if (ret < 0)
+ return ret;
+
+ if (enable)
+ ret |= M41T80_ALMON_SQWE;
+ else
+ ret &= ~M41T80_ALMON_SQWE;
+
+ return i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON, ret);
+}
+
+static int m41t80_sqw_prepare(struct clk_hw *hw)
+{
+ return m41t80_sqw_control(hw, 1);
+}
+
+static void m41t80_sqw_unprepare(struct clk_hw *hw)
+{
+ m41t80_sqw_control(hw, 0);
+}
+
+static int m41t80_sqw_is_prepared(struct clk_hw *hw)
+{
+ struct m41t80_data *m41t80 = sqw_to_m41t80_data(hw);
+ struct i2c_client *client = m41t80->client;
+ int ret = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_MON);
+
+ if (ret < 0)
+ return ret;
+
+ return !!(ret & M41T80_ALMON_SQWE);
+}
+
+static const struct clk_ops m41t80_sqw_ops = {
+ .prepare = m41t80_sqw_prepare,
+ .unprepare = m41t80_sqw_unprepare,
+ .is_prepared = m41t80_sqw_is_prepared,
+ .recalc_rate = m41t80_sqw_recalc_rate,
+ .round_rate = m41t80_sqw_round_rate,
+ .set_rate = m41t80_sqw_set_rate,
+};
+
+static struct clk *m41t80_sqw_register_clk(struct m41t80_data *m41t80)
+{
+ struct i2c_client *client = m41t80->client;
+ struct device_node *node = client->dev.of_node;
+ struct clk *clk;
+ struct clk_init_data init;
+ int ret;
+
+ /* First disable the clock */
+ ret = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_MON);
+ if (ret < 0)
+ return ERR_PTR(ret);
+ ret = i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON,
+ ret & ~(M41T80_ALMON_SQWE));
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ init.name = "m41t80-sqw";
+ init.ops = &m41t80_sqw_ops;
+ init.flags = 0;
+ init.parent_names = NULL;
+ init.num_parents = 0;
+ m41t80->sqw.init = &init;
+
+ /* optional override of the clockname */
+ of_property_read_string(node, "clock-output-names", &init.name);
+
+ /* register the clock */
+ clk = clk_register(&client->dev, &m41t80->sqw);
+ if (!IS_ERR(clk))
+ of_clk_add_provider(node, of_clk_src_simple_get, clk);
+
+ return clk;
+}
+#endif
+
#ifdef CONFIG_RTC_DRV_M41T80_WDT
/*
*****************************************************************************
@@ -713,6 +880,7 @@ static int m41t80_probe(struct i2c_client *client,
if (!m41t80_data)
return -ENOMEM;

+ m41t80_data->client = client;
m41t80_data->features = id->driver_data;
i2c_set_clientdata(client, m41t80_data);

@@ -801,6 +969,10 @@ static int m41t80_probe(struct i2c_client *client,
}
}
#endif
+#ifdef CONFIG_COMMON_CLK
+ if (m41t80_data->features & M41T80_FEATURE_SQ)
+ m41t80_sqw_register_clk(m41t80_data);
+#endif
return 0;
}

--
2.11.0

2017-04-25 14:46:23

by Gary Bisson

[permalink] [raw]
Subject: [PATCH 3/4] rtc: m41t80: remove sqw sysfs entry

In order to use the proper clock framework to control this feature.

Signed-off-by: Gary Bisson <[email protected]>
---
drivers/rtc/rtc-m41t80.c | 88 ------------------------------------------------
1 file changed, 88 deletions(-)

diff --git a/drivers/rtc/rtc-m41t80.c b/drivers/rtc/rtc-m41t80.c
index 0c142653c309..2bdf494a9bd4 100644
--- a/drivers/rtc/rtc-m41t80.c
+++ b/drivers/rtc/rtc-m41t80.c
@@ -387,96 +387,8 @@ static ssize_t flags_show(struct device *dev,
}
static DEVICE_ATTR_RO(flags);

-static ssize_t sqwfreq_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct i2c_client *client = to_i2c_client(dev);
- struct m41t80_data *clientdata = i2c_get_clientdata(client);
- int val, reg_sqw;
-
- if (!(clientdata->features & M41T80_FEATURE_SQ))
- return -EINVAL;
-
- reg_sqw = M41T80_REG_SQW;
- if (clientdata->features & M41T80_FEATURE_SQ_ALT)
- reg_sqw = M41T80_REG_WDAY;
- val = i2c_smbus_read_byte_data(client, reg_sqw);
- if (val < 0)
- return val;
- val = (val >> 4) & 0xf;
- switch (val) {
- case 0:
- break;
- case 1:
- val = 32768;
- break;
- default:
- val = 32768 >> val;
- }
- return sprintf(buf, "%d\n", val);
-}
-
-static ssize_t sqwfreq_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct i2c_client *client = to_i2c_client(dev);
- struct m41t80_data *clientdata = i2c_get_clientdata(client);
- int almon, sqw, reg_sqw, rc;
- unsigned long val;
-
- rc = kstrtoul(buf, 0, &val);
- if (rc < 0)
- return rc;
-
- if (!(clientdata->features & M41T80_FEATURE_SQ))
- return -EINVAL;
-
- if (val) {
- if (!is_power_of_2(val))
- return -EINVAL;
- val = ilog2(val);
- if (val == 15)
- val = 1;
- else if (val < 14)
- val = 15 - val;
- else
- return -EINVAL;
- }
- /* disable SQW, set SQW frequency & re-enable */
- almon = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_MON);
- if (almon < 0)
- return almon;
- reg_sqw = M41T80_REG_SQW;
- if (clientdata->features & M41T80_FEATURE_SQ_ALT)
- reg_sqw = M41T80_REG_WDAY;
- sqw = i2c_smbus_read_byte_data(client, reg_sqw);
- if (sqw < 0)
- return sqw;
- sqw = (sqw & 0x0f) | (val << 4);
-
- rc = i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON,
- almon & ~M41T80_ALMON_SQWE);
- if (rc < 0)
- return rc;
-
- if (val) {
- rc = i2c_smbus_write_byte_data(client, reg_sqw, sqw);
- if (rc < 0)
- return rc;
-
- rc = i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON,
- almon | M41T80_ALMON_SQWE);
- if (rc < 0)
- return rc;
- }
- return count;
-}
-static DEVICE_ATTR_RW(sqwfreq);
-
static struct attribute *attrs[] = {
&dev_attr_flags.attr,
- &dev_attr_sqwfreq.attr,
NULL,
};

--
2.11.0