Skip to content

Commit

Permalink
rtc: pcf8563: add CLKOUT to common clock framework
Browse files Browse the repository at this point in the history
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: Heiko Schocher <hs@denx.de>
Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
  • Loading branch information
hsdenx authored and alexandrebelloni committed Nov 8, 2015
1 parent dbb812b commit a39a640
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 1 deletion.
25 changes: 25 additions & 0 deletions Documentation/devicetree/bindings/rtc/pcf8563.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
* 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>;
...
};
170 changes: 169 additions & 1 deletion drivers/rtc/rtc-pcf8563.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -40,7 +41,14 @@

#define PCF8563_REG_AMN 0x09 /* alarm */

#define PCF8563_REG_CLKO 0x0D /* clock out */
#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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;

Expand Down

0 comments on commit a39a640

Please sign in to comment.