Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a39a6405 authored by Heiko Schocher's avatar Heiko Schocher Committed by Alexandre Belloni
Browse files

rtc: pcf8563: add CLKOUT to common clock framework



Add the clkout output clk to the common clock framework.
Disable the CLKOUT of the RTC after power-up.
After power-up/reset of the RTC, CLKOUT is enabled by default,
with CLKOUT enabled the RTC chip has 2-3 times higher power
consumption.

Signed-off-by: default avatarHeiko Schocher <hs@denx.de>
Signed-off-by: default avatarAlexandre Belloni <alexandre.belloni@free-electrons.com>
parent dbb812b1
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
* Philips PCF8563/Epson RTC8564 Real Time Clock

Philips PCF8563/Epson RTC8564 Real Time Clock

Required properties:
see: Documentation/devicetree/bindings/i2c/trivial-devices.txt

Optional property:
- #clock-cells: Should be 0.
- clock-output-names:
  overwrite the default clock name "pcf8563-clkout"

Example:

pcf8563: pcf8563@51 {
	compatible = "nxp,pcf8563";
	reg = <0x51>;
	#clock-cells = <0>;
};

device {
...
	clocks = <&pcf8563>;
...
};
+169 −1
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
 * published by the Free Software Foundation.
 */

#include <linux/clk-provider.h>
#include <linux/i2c.h>
#include <linux/bcd.h>
#include <linux/rtc.h>
@@ -41,6 +42,13 @@
#define PCF8563_REG_AMN		0x09 /* alarm */

#define PCF8563_REG_CLKO		0x0D /* clock out */
#define PCF8563_REG_CLKO_FE		0x80 /* clock out enabled */
#define PCF8563_REG_CLKO_F_MASK		0x03 /* frequenc mask */
#define PCF8563_REG_CLKO_F_32768HZ	0x00
#define PCF8563_REG_CLKO_F_1024HZ	0x01
#define PCF8563_REG_CLKO_F_32HZ		0x02
#define PCF8563_REG_CLKO_F_1HZ		0x03

#define PCF8563_REG_TMRC	0x0E /* timer control */
#define PCF8563_TMRC_ENABLE	BIT(7)
#define PCF8563_TMRC_4096	0
@@ -76,6 +84,9 @@ struct pcf8563 {
	int voltage_low; /* incicates if a low_voltage was detected */

	struct i2c_client *client;
#ifdef CONFIG_COMMON_CLK
	struct clk_hw		clkout_hw;
#endif
};

static int pcf8563_read_block_data(struct i2c_client *client, unsigned char reg,
@@ -390,6 +401,158 @@ static int pcf8563_irq_enable(struct device *dev, unsigned int enabled)
	return pcf8563_set_alarm_mode(to_i2c_client(dev), !!enabled);
}

#ifdef CONFIG_COMMON_CLK
/*
 * Handling of the clkout
 */

#define clkout_hw_to_pcf8563(_hw) container_of(_hw, struct pcf8563, clkout_hw)

static int clkout_rates[] = {
	32768,
	1024,
	32,
	1,
};

static unsigned long pcf8563_clkout_recalc_rate(struct clk_hw *hw,
						unsigned long parent_rate)
{
	struct pcf8563 *pcf8563 = clkout_hw_to_pcf8563(hw);
	struct i2c_client *client = pcf8563->client;
	unsigned char buf;
	int ret = pcf8563_read_block_data(client, PCF8563_REG_CLKO, 1, &buf);

	if (ret < 0)
		return 0;

	buf &= PCF8563_REG_CLKO_F_MASK;
	return clkout_rates[ret];
}

static long pcf8563_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
				      unsigned long *prate)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(clkout_rates); i++)
		if (clkout_rates[i] <= rate)
			return clkout_rates[i];

	return 0;
}

static int pcf8563_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
				   unsigned long parent_rate)
{
	struct pcf8563 *pcf8563 = clkout_hw_to_pcf8563(hw);
	struct i2c_client *client = pcf8563->client;
	unsigned char buf;
	int ret = pcf8563_read_block_data(client, PCF8563_REG_CLKO, 1, &buf);
	int i;

	if (ret < 0)
		return ret;

	for (i = 0; i < ARRAY_SIZE(clkout_rates); i++)
		if (clkout_rates[i] == rate) {
			buf &= ~PCF8563_REG_CLKO_F_MASK;
			buf |= i;
			ret = pcf8563_write_block_data(client,
						       PCF8563_REG_CLKO, 1,
						       &buf);
			return ret;
		}

	return -EINVAL;
}

static int pcf8563_clkout_control(struct clk_hw *hw, bool enable)
{
	struct pcf8563 *pcf8563 = clkout_hw_to_pcf8563(hw);
	struct i2c_client *client = pcf8563->client;
	unsigned char buf;
	int ret = pcf8563_read_block_data(client, PCF8563_REG_CLKO, 1, &buf);

	if (ret < 0)
		return ret;

	if (enable)
		buf |= PCF8563_REG_CLKO_FE;
	else
		buf &= ~PCF8563_REG_CLKO_FE;

	ret = pcf8563_write_block_data(client, PCF8563_REG_CLKO, 1, &buf);
	return ret;
}

static int pcf8563_clkout_prepare(struct clk_hw *hw)
{
	return pcf8563_clkout_control(hw, 1);
}

static void pcf8563_clkout_unprepare(struct clk_hw *hw)
{
	pcf8563_clkout_control(hw, 0);
}

static int pcf8563_clkout_is_prepared(struct clk_hw *hw)
{
	struct pcf8563 *pcf8563 = clkout_hw_to_pcf8563(hw);
	struct i2c_client *client = pcf8563->client;
	unsigned char buf;
	int ret = pcf8563_read_block_data(client, PCF8563_REG_CLKO, 1, &buf);

	if (ret < 0)
		return ret;

	return !!(buf & PCF8563_REG_CLKO_FE);
}

static const struct clk_ops pcf8563_clkout_ops = {
	.prepare = pcf8563_clkout_prepare,
	.unprepare = pcf8563_clkout_unprepare,
	.is_prepared = pcf8563_clkout_is_prepared,
	.recalc_rate = pcf8563_clkout_recalc_rate,
	.round_rate = pcf8563_clkout_round_rate,
	.set_rate = pcf8563_clkout_set_rate,
};

static struct clk *pcf8563_clkout_register_clk(struct pcf8563 *pcf8563)
{
	struct i2c_client *client = pcf8563->client;
	struct device_node *node = client->dev.of_node;
	struct clk *clk;
	struct clk_init_data init;
	int ret;
	unsigned char buf;

	/* disable the clkout output */
	buf = 0;
	ret = pcf8563_write_block_data(client, PCF8563_REG_CLKO, 1, &buf);
	if (ret < 0)
		return ERR_PTR(ret);

	init.name = "pcf8563-clkout";
	init.ops = &pcf8563_clkout_ops;
	init.flags = CLK_IS_ROOT;
	init.parent_names = NULL;
	init.num_parents = 0;
	pcf8563->clkout_hw.init = &init;

	/* optional override of the clockname */
	of_property_read_string(node, "clock-output-names", &init.name);

	/* register the clock */
	clk = devm_clk_register(&client->dev, &pcf8563->clkout_hw);

	if (!IS_ERR(clk))
		of_clk_add_provider(node, of_clk_src_simple_get, clk);

	return clk;
}
#endif

static const struct rtc_class_ops pcf8563_rtc_ops = {
	.ioctl		= pcf8563_rtc_ioctl,
	.read_time	= pcf8563_rtc_read_time,
@@ -459,6 +622,11 @@ static int pcf8563_probe(struct i2c_client *client,

	}

#ifdef CONFIG_COMMON_CLK
	/* register clk in common clk framework */
	pcf8563_clkout_register_clk(pcf8563);
#endif

	/* the pcf8563 alarm only supports a minute accuracy */
	pcf8563->rtc->uie_unsupported = 1;