Commit f5b08ac7 authored by Oleg Dzhimiev's avatar Oleg Dzhimiev
Browse files

updated driver to current version, added our fall-through

parent 27a7df33
Loading
Loading
Loading
Loading
+248 −118
Original line number Diff line number Diff line
@@ -16,10 +16,12 @@
#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>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/rtc.h>
#include <linux/slab.h>
#include <linux/mutex.h>
@@ -52,6 +54,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 */
@@ -86,9 +90,71 @@ static const struct i2c_device_id m41t80_id[] = {
};
MODULE_DEVICE_TABLE(i2c, m41t80_id);

static const struct of_device_id m41t80_of_match[] = {
	{
		.compatible = "st,m41t62",
		.data = (void *)(M41T80_FEATURE_SQ | M41T80_FEATURE_SQ_ALT)
	},
	{
		.compatible = "st,m41t65",
		.data = (void *)(M41T80_FEATURE_HT | M41T80_FEATURE_WD)
	},
	{
		.compatible = "st,m41t80",
		.data = (void *)(M41T80_FEATURE_SQ)
	},
	{
		.compatible = "st,m41t81",
		.data = (void *)(M41T80_FEATURE_HT | M41T80_FEATURE_SQ)
	},
	{
		.compatible = "st,m41t81s",
		.data = (void *)(M41T80_FEATURE_HT | M41T80_FEATURE_BL | M41T80_FEATURE_SQ)
	},
	{
		.compatible = "st,m41t82",
		.data = (void *)(M41T80_FEATURE_HT | M41T80_FEATURE_BL | M41T80_FEATURE_SQ)
	},
	{
		.compatible = "st,m41t83",
		.data = (void *)(M41T80_FEATURE_HT | M41T80_FEATURE_BL | M41T80_FEATURE_SQ)
	},
	{
		.compatible = "st,m41t84",
		.data = (void *)(M41T80_FEATURE_HT | M41T80_FEATURE_BL | M41T80_FEATURE_SQ)
	},
	{
		.compatible = "st,m41t85",
		.data = (void *)(M41T80_FEATURE_HT | M41T80_FEATURE_BL | M41T80_FEATURE_SQ)
	},
	{
		.compatible = "st,m41t87",
		.data = (void *)(M41T80_FEATURE_HT | M41T80_FEATURE_BL | M41T80_FEATURE_SQ)
	},
	{
		.compatible = "microcrystal,rv4162",
		.data = (void *)(M41T80_FEATURE_SQ | M41T80_FEATURE_WD | M41T80_FEATURE_SQ_ALT)
	},
	/* DT compatibility only, do not use compatibles below: */
	{
		.compatible = "st,rv4162",
		.data = (void *)(M41T80_FEATURE_SQ | M41T80_FEATURE_WD | M41T80_FEATURE_SQ_ALT)
	},
	{
		.compatible = "rv4162",
		.data = (void *)(M41T80_FEATURE_SQ | M41T80_FEATURE_WD | M41T80_FEATURE_SQ_ALT)
	},
	{ }
};
MODULE_DEVICE_TABLE(of, m41t80_of_match);

struct m41t80_data {
	u8 features;
	unsigned long 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)
@@ -142,7 +208,7 @@ static int m41t80_get_datetime(struct i2c_client *client,
		return flags;

	if (flags & M41T80_FLAGS_OF) {
		dev_err(&client->dev, "Oscillator failure, data might be invalid. Elphel: proceeding anyway.\n");
		dev_err(&client->dev, "Oscillator failure, data is invalid. Elphel: proceeding anyway.\n");
		//return -EINVAL;
	}

@@ -168,6 +234,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 +250,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) {
@@ -273,6 +351,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;
@@ -359,115 +440,165 @@ static int m41t80_resume(struct device *dev)

static SIMPLE_DEV_PM_OPS(m41t80_pm, m41t80_suspend, m41t80_resume);

static ssize_t flags_show(struct device *dev,
			  struct device_attribute *attr, char *buf)
#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 i2c_client *client = to_i2c_client(dev);
	int val;
	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);

	val = i2c_smbus_read_byte_data(client, M41T80_REG_FLAGS);
	if (val < 0)
	return val;
	return sprintf(buf, "%#x\n", val);
}
static DEVICE_ATTR_RO(flags);

static ssize_t sqwfreq_show(struct device *dev,
			    struct device_attribute *attr, char *buf)
static long m41t80_sqw_round_rate(struct clk_hw *hw, unsigned long rate,
				  unsigned long *prate)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct m41t80_data *clientdata = i2c_get_clientdata(client);
	int val, reg_sqw;
	int i, freq = M41T80_SQW_MAX_FREQ;

	if (!(clientdata->features & M41T80_FEATURE_SQ))
		return -EINVAL;
	if (freq <= rate)
		return freq;

	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;
	for (i = 2; i <= ilog2(M41T80_SQW_MAX_FREQ); i++) {
		freq /= 1 << i;
		if (freq <= rate)
			return freq;
	}
	return sprintf(buf, "%d\n", val);

	return 0;
}

static ssize_t sqwfreq_store(struct device *dev,
			     struct device_attribute *attr,
			     const char *buf, size_t count)
static int m41t80_sqw_set_rate(struct clk_hw *hw, unsigned long rate,
			       unsigned long parent_rate)
{
	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;
	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;
	}

	rc = kstrtoul(buf, 0, &val);
	if (rc < 0)
		return rc;
	reg = i2c_smbus_read_byte_data(client, reg_sqw);
	if (reg < 0)
		return reg;

	if (!(clientdata->features & M41T80_FEATURE_SQ))
		return -EINVAL;
	reg = (reg & 0x0f) | (val << 4);

	ret = i2c_smbus_write_byte_data(client, reg_sqw, reg);
	if (ret < 0)
		return ret;

	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;
}

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
			return -EINVAL;
		ret &= ~M41T80_ALMON_SQWE;

	return i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON, ret);
}
	/* 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;
static int m41t80_sqw_prepare(struct clk_hw *hw)
{
	return m41t80_sqw_control(hw, 1);
}

		rc = i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON,
					       almon | M41T80_ALMON_SQWE);
		if (rc < 0)
			return rc;
static void m41t80_sqw_unprepare(struct clk_hw *hw)
{
	m41t80_sqw_control(hw, 0);
}
	return count;

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 DEVICE_ATTR_RW(sqwfreq);

static struct attribute *attrs[] = {
	&dev_attr_flags.attr,
	&dev_attr_sqwfreq.attr,
	NULL,
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 attribute_group attr_group = {
	.attrs = attrs,
};
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
/*
@@ -759,13 +890,6 @@ static struct notifier_block wdt_notifier = {
 *****************************************************************************
 */

static void m41t80_remove_sysfs_group(void *_dev)
{
	struct device *dev = _dev;

	sysfs_remove_group(&dev->kobj, &attr_group);
}

static int m41t80_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
@@ -774,6 +898,7 @@ static int m41t80_probe(struct i2c_client *client,
	struct rtc_device *rtc = NULL;
	struct rtc_time tm;
	struct m41t80_data *m41t80_data = NULL;
	bool wakeup_source = false;

	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK |
				     I2C_FUNC_SMBUS_BYTE_DATA)) {
@@ -786,9 +911,18 @@ static int m41t80_probe(struct i2c_client *client,
	if (!m41t80_data)
		return -ENOMEM;

	m41t80_data->client = client;
	if (client->dev.of_node)
		m41t80_data->features = (unsigned long)
			of_device_get_match_data(&client->dev);
	else
		m41t80_data->features = id->driver_data;
	i2c_set_clientdata(client, m41t80_data);

#ifdef CONFIG_OF
	wakeup_source = of_property_read_bool(client->dev.of_node,
					      "wakeup-source");
#endif
	if (client->irq > 0) {
		rc = devm_request_threaded_irq(&client->dev, client->irq,
					       NULL, m41t80_handle_irq,
@@ -797,14 +931,16 @@ static int m41t80_probe(struct i2c_client *client,
		if (rc) {
			dev_warn(&client->dev, "unable to request IRQ, alarms disabled\n");
			client->irq = 0;
		} else {
			wakeup_source = false;
		}
	}
	if (client->irq > 0 || wakeup_source) {
		m41t80_rtc_ops.read_alarm = m41t80_read_alarm;
		m41t80_rtc_ops.set_alarm = m41t80_set_alarm;
		m41t80_rtc_ops.alarm_irq_enable = m41t80_alarm_irq_enable;
		/* Enable the wakealarm */
		device_init_wakeup(&client->dev, true);
	}
	}

	rtc = devm_rtc_device_register(&client->dev, client->name,
				       &m41t80_rtc_ops, THIS_MODULE);
@@ -812,6 +948,10 @@ static int m41t80_probe(struct i2c_client *client,
		return PTR_ERR(rtc);

	m41t80_data->rtc = rtc;
	if (client->irq <= 0) {
		/* We cannot support UIE mode if we do not have an IRQ line */
		rtc->uie_unsupported = 1;
	}

	/* Make sure HT (Halt Update) bit is cleared */
	rc = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_HOUR);
@@ -846,21 +986,6 @@ static int m41t80_probe(struct i2c_client *client,
		return rc;
	}

	/* Export sysfs entries */
	rc = sysfs_create_group(&(&client->dev)->kobj, &attr_group);
	if (rc) {
		dev_err(&client->dev, "Failed to create sysfs group: %d\n", rc);
		return rc;
	}

	rc = devm_add_action_or_reset(&client->dev, m41t80_remove_sysfs_group,
				      &client->dev);
	if (rc) {
		dev_err(&client->dev,
			"Failed to add sysfs cleanup action: %d\n", rc);
		return rc;
	}

#ifdef CONFIG_RTC_DRV_M41T80_WDT
	if (m41t80_data->features & M41T80_FEATURE_HT) {
		save_client = client;
@@ -873,6 +998,10 @@ static int m41t80_probe(struct i2c_client *client,
			return rc;
		}
	}
#endif
#ifdef CONFIG_COMMON_CLK
	if (m41t80_data->features & M41T80_FEATURE_SQ)
		m41t80_sqw_register_clk(m41t80_data);
#endif
	return 0;
}
@@ -894,6 +1023,7 @@ static int m41t80_remove(struct i2c_client *client)
static struct i2c_driver m41t80_driver = {
	.driver = {
		.name = "rtc-m41t80",
		.of_match_table = of_match_ptr(m41t80_of_match),
		.pm = &m41t80_pm,
	},
	.probe = m41t80_probe,