Message ID | 20160801155034.32232.68458.stgit@localhost |
---|---|
State | Changes Requested |
Headers | show |
Hi, Sorry for the very late review. The driver seems good to me but I'd like a review from the DT maintainers before taking it. On 01/08/2016 at 17:50:34 +0200, Martin Fuzzey wrote : > Add basic support for NXP PCF85263 I2C RTC chip. > > Only RTC and Alarm functions are supported. > > Signed-off-by: Martin Fuzzey <mfuzzey@parkeon.com> > --- > drivers/rtc/Kconfig | 8 + > drivers/rtc/Makefile | 1 > drivers/rtc/rtc-pcf85263.c | 655 ++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 664 insertions(+) > create mode 100644 drivers/rtc/rtc-pcf85263.c > > diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig > index 18639e0..63ec0fc 100644 > --- a/drivers/rtc/Kconfig > +++ b/drivers/rtc/Kconfig > @@ -396,6 +396,14 @@ config RTC_DRV_PCF85063 > This driver can also be built as a module. If so, the module > will be called rtc-pcf85063. > > +config RTC_DRV_PCF85263 > + tristate "NXP PCF85263" > + help > + If you say yes here you get support for the PCF85263 RTC chip > + > + This driver can also be built as a module. If so, the module > + will be called rtc-pcf85263. > + > config RTC_DRV_PCF8563 > tristate "Philips PCF8563/Epson RTC8564" > help > diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile > index ea28337..04f48fd 100644 > --- a/drivers/rtc/Makefile > +++ b/drivers/rtc/Makefile > @@ -108,6 +108,7 @@ obj-$(CONFIG_RTC_DRV_PCF2123) += rtc-pcf2123.o > obj-$(CONFIG_RTC_DRV_PCF2127) += rtc-pcf2127.o > obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o > obj-$(CONFIG_RTC_DRV_PCF85063) += rtc-pcf85063.o > +obj-$(CONFIG_RTC_DRV_PCF85263) += rtc-pcf85263.o > obj-$(CONFIG_RTC_DRV_PCF8523) += rtc-pcf8523.o > obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o > obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o > diff --git a/drivers/rtc/rtc-pcf85263.c b/drivers/rtc/rtc-pcf85263.c > new file mode 100644 > index 0000000..997742d > --- /dev/null > +++ b/drivers/rtc/rtc-pcf85263.c > @@ -0,0 +1,655 @@ > +/* > + * rtc-pcf85263 Driver for the NXP PCF85263 RTC > + * > + * Copyright 2016 Parkeon > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + */ > + > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/rtc.h> > +#include <linux/i2c.h> > +#include <linux/bcd.h> > +#include <linux/of.h> > +#include <linux/of_device.h> > +#include <linux/regmap.h> > + > +#include <dt-bindings/rtc/nxp,pcf85263.h> > + > +#define DRV_NAME "rtc-pcf85263" > + > +#define PCF85263_REG_RTC_SC 0x01 /* Seconds */ > +#define PCF85263_REG_RTC_SC_OS BIT(7) /* Oscilator stopped flag */ > + > +#define PCF85263_REG_RTC_MN 0x02 /* Minutes */ > +#define PCF85263_REG_RTC_HR 0x03 /* Hours */ > +#define PCF85263_REG_RTC_DT 0x04 /* Day of month 1-31 */ > +#define PCF85263_REG_RTC_DW 0x05 /* Day of week 0-6 */ > +#define PCF85263_REG_RTC_MO 0x06 /* Month 1-12 */ > +#define PCF85263_REG_RTC_YR 0x07 /* Year 0-99 */ > + > +#define PCF85263_REG_ALM1_SC 0x08 /* Seconds */ > +#define PCF85263_REG_ALM1_MN 0x09 /* Minutes */ > +#define PCF85263_REG_ALM1_HR 0x0a /* Hours */ > +#define PCF85263_REG_ALM1_DT 0x0b /* Day of month 1-31 */ > +#define PCF85263_REG_ALM1_MO 0x0c /* Month 1-12 */ > + > +#define PCF85263_REG_ALM_CTL 0x10 > +#define PCF85263_REG_ALM_CTL_ALL_A1E 0x1f /* sec,min,hr,day,mon alarm 1 */ > + > +#define PCF85263_REG_OSC 0x25 > +#define PCF85263_REG_OSC_CL_MASK (BIT(0) | BIT(1)) > +#define PCF85263_REG_OSC_CL_SHIFT 0 > +#define PCF85263_REG_OSC_OSCD_MASK (BIT(2) | BIT(3)) > +#define PCF85263_REG_OSC_OSCD_SHIFT 2 > +#define PCF85263_REG_OSC_LOWJ BIT(4) > +#define PCF85263_REG_OSC_12H BIT(5) > + > +#define PCF85263_REG_PINIO 0x27 > +#define PCF85263_REG_PINIO_INTAPM_MASK (BIT(0) | BIT(1)) > +#define PCF85263_REG_PINIO_INTAPM_SHIFT 0 > +#define PCF85263_INTAPM_INTA (0x2 << PCF85263_REG_PINIO_INTAPM_SHIFT) > +#define PCF85263_INTAPM_HIGHZ (0x3 << PCF85263_REG_PINIO_INTAPM_SHIFT) > +#define PCF85263_REG_PINIO_TSPM_MASK (BIT(2) | BIT(3)) > +#define PCF85263_REG_PINIO_TSPM_SHIFT 2 > +#define PCF85263_TSPM_DISABLED (0x0 << PCF85263_REG_PINIO_TSPM_SHIFT) > +#define PCF85263_TSPM_INTB (0x1 << PCF85263_REG_PINIO_TSPM_SHIFT) > +#define PCF85263_REG_PINIO_CLKDISABLE BIT(7) > + > +#define PCF85263_REG_FUNCTION 0x28 > +#define PCF85263_REG_FUNCTION_COF_MASK 0x7 > +#define PCF85263_REG_FUNCTION_COF_OFF 0x7 /* No clock output */ > + > +#define PCF85263_REG_INTA_CTL 0x29 > +#define PCF85263_REG_INTB_CTL 0x2A > +#define PCF85263_REG_INTx_CTL_A1E BIT(4) /* Alarm 1 */ > +#define PCF85263_REG_INTx_CTL_ILP BIT(7) /* 0=pulse, 1=level */ > + > +#define PCF85263_REG_FLAGS 0x2B > +#define PCF85263_REG_FLAGS_A1F BIT(5) > + > +#define PCF85263_REG_RAM_BYTE 0x2c > + > +#define PCF85263_REG_STOPENABLE 0x2e > +#define PCF85263_REG_STOPENABLE_STOP BIT(0) > + > +#define PCF85263_REG_RESET 0x2f /* Reset command */ > +#define PCF85263_REG_RESET_CMD_CPR 0xa4 /* Clear prescaler */ > + > +#define PCF85263_MAX_REG 0x2f > + > +#define PCF85263_HR_PM BIT(5) > + > +enum pcf85263_irqpin { > + PCF85263_IRQPIN_NONE, > + PCF85263_IRQPIN_INTA, > + PCF85263_IRQPIN_INTB > +}; > + > +static const char *const pcf85263_irqpin_names[] = { > + [PCF85263_IRQPIN_NONE] = "None", > + [PCF85263_IRQPIN_INTA] = "INTA", > + [PCF85263_IRQPIN_INTB] = "INTB" > +}; > + > +struct pcf85263 { > + struct device *dev; > + struct rtc_device *rtc; > + struct regmap *regmap; > + enum pcf85263_irqpin irq_pin; > + int irq; > + bool mode_12h; > +}; > + > +/* > + * Helpers to convert 12h to 24h and vice versa. > + * Values in register are stored in BCD with a PM flag in bit 5 > + * > + * 23:00 <=> 11PM <=> 0x31 > + * 00:00 <=> 12AM <=> 0x12 > + * 01:00 <=> 1AM <=> 0x01 > + * 12:00 <=> 12PM <=> 0x32 > + * 13:00 <=> 1PM <=> 0x21 > + */ > +static int pcf85263_bcd12h_to_bin24h(int regval) > +{ > + int hr = bcd2bin(regval & 0x1f); > + bool pm = regval & PCF85263_HR_PM; > + > + if (hr == 12) > + return pm ? 12 : 0; > + > + return pm ? hr + 12 : hr; > +} > + > +static int pcf85263_bin24h_to_bcd12h(int hr24) > +{ > + bool pm = hr24 >= 12; > + int hr12 = hr24 % 12; > + > + if (!hr12) > + hr12++; > + > + return bin2bcd(hr12) | pm ? 0 : PCF85263_HR_PM; > +} > + > +static int pcf85263_read_time(struct device *dev, struct rtc_time *tm) > +{ > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > + const int first = PCF85263_REG_RTC_SC; > + const int last = PCF85263_REG_RTC_YR; > + const int len = last - first + 1; > + u8 regs[len]; > + u8 hr_reg; > + int ret; > + > + ret = regmap_bulk_read(pcf85263->regmap, first, regs, len); > + if (ret) > + return ret; > + > + if (regs[PCF85263_REG_RTC_SC - first] & PCF85263_REG_RTC_SC_OS) { > + dev_warn(dev, "Oscillator stop detected, date/time is not reliable.\n"); > + return -EINVAL; > + } > + > + tm->tm_sec = bcd2bin(regs[PCF85263_REG_RTC_SC - first] & 0x7f); > + tm->tm_min = bcd2bin(regs[PCF85263_REG_RTC_MN - first] & 0x7f); > + > + hr_reg = regs[PCF85263_REG_RTC_HR - first]; > + if (pcf85263->mode_12h) > + tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg); > + else > + tm->tm_hour = bcd2bin(hr_reg & 0x3f); > + > + tm->tm_mday = bcd2bin(regs[PCF85263_REG_RTC_DT - first]); > + tm->tm_wday = bcd2bin(regs[PCF85263_REG_RTC_DW - first]); > + tm->tm_mon = bcd2bin(regs[PCF85263_REG_RTC_MO - first]) - 1; > + tm->tm_year = bcd2bin(regs[PCF85263_REG_RTC_YR - first]); > + > + tm->tm_year += 100; /* Assume 21st century */ > + > + return 0; > +} > + > +static int pcf85263_set_time(struct device *dev, struct rtc_time *tm) > +{ > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > + > + /* > + * Before setting time need to stop RTC and disable prescaler > + * Do this all in a single I2C transaction exploiting wraparound > + * as described in data sheet. > + * This means that the array below must be in register order > + */ > + u8 regs[] = { > + PCF85263_REG_STOPENABLE_STOP, /* STOP */ > + PCF85263_REG_RESET_CMD_CPR, /* Disable prescaler */ > + /* Wrap around to register 0 (1/100s) */ > + 0, /* 1/100s always zero. */ > + bin2bcd(tm->tm_sec), > + bin2bcd(tm->tm_min), > + bin2bcd(tm->tm_hour), /* 24-hour */ > + bin2bcd(tm->tm_mday), > + bin2bcd(tm->tm_wday + 1), > + bin2bcd(tm->tm_mon + 1), > + bin2bcd(tm->tm_year % 100) > + }; > + int ret; > + > + ret = regmap_bulk_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, > + regs, sizeof(regs)); > + if (ret) > + return ret; > + > + /* As we have set the time in 24H update the hardware for that */ > + if (pcf85263->mode_12h) { > + pcf85263->mode_12h = false; > + ret = regmap_update_bits(pcf85263->regmap, PCF85263_REG_OSC, > + PCF85263_REG_OSC_12H, 0); > + if (ret) > + return ret; > + } > + > + /* Start it again */ > + return regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, 0); > +} > + > +static int pcf85263_enable_alarm(struct pcf85263 *pcf85263, bool enable) > +{ > + int reg; > + int ret; > + > + ret = regmap_update_bits(pcf85263->regmap, PCF85263_REG_ALM_CTL, > + PCF85263_REG_ALM_CTL_ALL_A1E, > + enable ? PCF85263_REG_ALM_CTL_ALL_A1E : 0); > + if (ret) > + return ret; > + > + switch (pcf85263->irq_pin) { > + case PCF85263_IRQPIN_NONE: > + return 0; > + > + case PCF85263_IRQPIN_INTA: > + reg = PCF85263_REG_INTA_CTL; > + break; > + > + case PCF85263_IRQPIN_INTB: > + reg = PCF85263_REG_INTB_CTL; > + break; > + > + default: > + return -EINVAL; > + } > + > + return regmap_update_bits(pcf85263->regmap, reg, > + PCF85263_REG_INTx_CTL_A1E, > + enable ? PCF85263_REG_INTx_CTL_A1E : 0); > +} > + > +static int pcf85263_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) > +{ > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > + struct rtc_time *tm = &alarm->time; > + const int first = PCF85263_REG_ALM1_SC; > + const int last = PCF85263_REG_ALM1_MO; > + const int len = last - first + 1; > + u8 regs[len]; > + u8 hr_reg; > + unsigned int regval; > + int ret; > + > + ret = regmap_bulk_read(pcf85263->regmap, first, regs, len); > + if (ret) > + return ret; > + > + tm->tm_sec = bcd2bin(regs[PCF85263_REG_ALM1_SC - first] & 0x7f); > + tm->tm_min = bcd2bin(regs[PCF85263_REG_ALM1_MN - first] & 0x7f); > + > + hr_reg = regs[PCF85263_REG_ALM1_HR - first]; > + if (pcf85263->mode_12h) > + tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg); > + else > + tm->tm_hour = bcd2bin(hr_reg & 0x3f); > + > + tm->tm_mday = bcd2bin(regs[PCF85263_REG_ALM1_DT - first]); > + tm->tm_mon = bcd2bin(regs[PCF85263_REG_ALM1_MO - first]) - 1; > + tm->tm_year = -1; > + tm->tm_wday = -1; > + > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_ALM_CTL, ®val); > + if (ret) > + return ret; > + alarm->enabled = !!(regval & PCF85263_REG_ALM_CTL_ALL_A1E); > + > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS, ®val); > + if (ret) > + return ret; > + alarm->pending = !!(regval & PCF85263_REG_FLAGS_A1F); > + > + return 0; > +} > + > +static int pcf85263_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) > +{ > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > + struct rtc_time *tm = &alarm->time; > + const int first = PCF85263_REG_ALM1_SC; > + const int last = PCF85263_REG_ALM1_MO; > + const int len = last - first + 1; > + u8 regs[len]; > + int ret; > + > + /* Disable alarm comparison during update */ > + ret = pcf85263_enable_alarm(pcf85263, false); > + if (ret) > + return ret; > + > + /* Clear any pending alarm (write 0=>clr, 1=>no change) */ > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS, > + ~PCF85263_REG_FLAGS_A1F); > + if (ret) > + return ret; > + > + /* Set the alarm time registers */ > + regs[PCF85263_REG_ALM1_SC - first] = bin2bcd(tm->tm_sec); > + regs[PCF85263_REG_ALM1_MN - first] = bin2bcd(tm->tm_min); > + regs[PCF85263_REG_ALM1_HR - first] = pcf85263->mode_12h ? > + pcf85263_bin24h_to_bcd12h(tm->tm_hour) : > + bin2bcd(tm->tm_hour); > + regs[PCF85263_REG_ALM1_DT - first] = bin2bcd(tm->tm_mday); > + regs[PCF85263_REG_ALM1_MO - first] = bin2bcd(tm->tm_mon + 1); > + > + ret = regmap_bulk_write(pcf85263->regmap, first, regs, sizeof(regs)); > + if (ret) > + return ret; > + > + if (alarm->enabled) > + ret = pcf85263_enable_alarm(pcf85263, true); > + > + return ret; > +} > + > +static int pcf85263_alarm_irq_enable(struct device *dev, unsigned int enable) > +{ > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > + > + return pcf85263_enable_alarm(pcf85263, !!enable); > +} > + > +static irqreturn_t pcf85263_irq(int irq, void *data) > +{ > + struct pcf85263 *pcf85263 = data; > + unsigned int regval; > + int ret; > + > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS, ®val); > + if (ret) > + return IRQ_NONE; > + > + if (regval & PCF85263_REG_FLAGS_A1F) { > + regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS, > + ~PCF85263_REG_FLAGS_A1F); > + > + rtc_update_irq(pcf85263->rtc, 1, RTC_IRQF | RTC_AF); > + > + return IRQ_HANDLED; > + } > + > + return IRQ_NONE; > +} > + > +static int pcf85263_check_osc_stopped(struct pcf85263 *pcf85263) > +{ > + unsigned int regval; > + int ret; > + > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_RTC_SC, ®val); > + if (ret) > + return ret; > + > + ret = regval & PCF85263_REG_RTC_SC_OS ? 1 : 0; > + if (ret) > + dev_warn(pcf85263->dev, "Oscillator stop detected, date/time is not reliable.\n"); > + > + return ret; > +} > + > +#ifdef CONFIG_RTC_INTF_DEV > +static int pcf85263_ioctl(struct device *dev, > + unsigned int cmd, unsigned long arg) > +{ > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > + int ret; > + > + switch (cmd) { > + case RTC_VL_READ: > + ret = pcf85263_check_osc_stopped(pcf85263); > + if (ret < 0) > + return ret; > + > + if (copy_to_user((void __user *)arg, &ret, sizeof(int))) > + return -EFAULT; > + return 0; > + > + case RTC_VL_CLR: > + return regmap_update_bits(pcf85263->regmap, > + PCF85263_REG_RTC_SC, > + PCF85263_REG_RTC_SC_OS, 0); > + default: > + return -ENOIOCTLCMD; > + } > +} > +#else > +#define pcf85263_ioctl NULL > +#endif > + > +static int pcf85263_init_hw(struct pcf85263 *pcf85263) > +{ > + struct device_node *np = pcf85263->dev->of_node; > + unsigned int regval; > + u32 propval; > + int ret; > + > + /* Determine if oscilator has been stopped (probably low power) */ > + ret = pcf85263_check_osc_stopped(pcf85263); > + if (ret < 0) { > + /* Log here since this is the first hw access on probe */ > + dev_err(pcf85263->dev, "Unable to read register\n"); > + > + return ret; > + } > + > + /* Determine 12/24H mode */ > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_OSC, ®val); > + if (ret) > + return ret; > + pcf85263->mode_12h = !!(regval & PCF85263_REG_OSC_12H); > + > + /* Set oscilator register */ > + regval &= ~PCF85263_REG_OSC_12H; /* keep current 12/24 h setting */ > + > + propval = PCF85263_QUARTZCAP_12p5pF; > + of_property_read_u32(np, "quartz-load-capacitance", &propval); > + regval |= ((propval << PCF85263_REG_OSC_CL_SHIFT) > + & PCF85263_REG_OSC_CL_MASK); > + > + propval = PCF85263_QUARTZDRIVE_NORMAL; > + of_property_read_u32(np, "quartz-drive-strength", &propval); > + regval |= ((propval << PCF85263_REG_OSC_OSCD_SHIFT) > + & PCF85263_REG_OSC_OSCD_MASK); > + > + if (of_property_read_bool(np, "quartz-low-jitter")) > + regval |= PCF85263_REG_OSC_LOWJ; > + > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_OSC, regval); > + if (ret) > + return ret; > + > + /* Set function register (RTC mode, 1s tick, clock output static) */ > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_FUNCTION, > + PCF85263_REG_FUNCTION_COF_OFF); > + if (ret) > + return ret; > + > + /* Set all interrupts to disabled, level mode */ > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTA_CTL, > + PCF85263_REG_INTx_CTL_ILP); > + if (ret) > + return ret; > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTB_CTL, > + PCF85263_REG_INTx_CTL_ILP); > + if (ret) > + return ret; > + > + /* Setup IO pin config register */ > + regval = PCF85263_REG_PINIO_CLKDISABLE; > + switch (pcf85263->irq_pin) { > + case PCF85263_IRQPIN_INTA: > + regval |= (PCF85263_INTAPM_INTA | PCF85263_TSPM_DISABLED); > + break; > + case PCF85263_IRQPIN_INTB: > + regval |= (PCF85263_INTAPM_HIGHZ | PCF85263_TSPM_INTB); > + break; > + case PCF85263_IRQPIN_NONE: > + regval |= (PCF85263_INTAPM_HIGHZ | PCF85263_TSPM_DISABLED); > + break; > + } > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_PINIO, regval); > + > + return ret; > +} > + > +static const struct rtc_class_ops rtc_ops = { > + .ioctl = pcf85263_ioctl, > + .read_time = pcf85263_read_time, > + .set_time = pcf85263_set_time, > + .read_alarm = pcf85263_read_alarm, > + .set_alarm = pcf85263_set_alarm, > + .alarm_irq_enable = pcf85263_alarm_irq_enable, > +}; > + > +static const struct regmap_config pcf85263_regmap_cfg = { > + .reg_bits = 8, > + .val_bits = 8, > + .max_register = PCF85263_MAX_REG, > +}; > + > +/* > + * On some boards the interrupt line may not be wired to the CPU but only to > + * a power supply circuit. > + * In that case no interrupt will be specified in the device tree but the > + * wakeup-source DT property may be used to enable wakeup programming in > + * sysfs > + */ > +static bool pcf85263_can_wakeup_machine(struct pcf85263 *pcf85263) > +{ > + return pcf85263->irq || > + of_property_read_bool(pcf85263->dev->of_node, "wakeup-source"); > +} > + > +static int pcf85263_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &client->dev; > + struct pcf85263 *pcf85263; > + int ret; > + > + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | > + I2C_FUNC_SMBUS_BYTE_DATA | > + I2C_FUNC_SMBUS_I2C_BLOCK)) > + return -ENODEV; > + > + pcf85263 = devm_kzalloc(dev, sizeof(*pcf85263), GFP_KERNEL); > + if (!pcf85263) > + return -ENOMEM; > + > + pcf85263->dev = dev; > + pcf85263->irq = client->irq; > + dev_set_drvdata(dev, pcf85263); > + > + pcf85263->regmap = devm_regmap_init_i2c(client, &pcf85263_regmap_cfg); > + if (IS_ERR(pcf85263->regmap)) { > + ret = PTR_ERR(pcf85263->regmap); > + dev_err(dev, "regmap allocation failed (%d)\n", ret); > + > + return ret; > + } > + > + /* Determine which interrupt pin the board uses */ > + if (pcf85263_can_wakeup_machine(pcf85263)) { > + if (of_property_match_string(dev->of_node, > + "interrupt-names", "INTB") >= 0) > + pcf85263->irq_pin = PCF85263_IRQPIN_INTB; > + else > + pcf85263->irq_pin = PCF85263_IRQPIN_INTA; > + } else { > + pcf85263->irq_pin = PCF85263_IRQPIN_NONE; > + } > + > + ret = pcf85263_init_hw(pcf85263); > + if (ret) > + return ret; > + > + if (pcf85263->irq) { > + ret = devm_request_threaded_irq(dev, pcf85263->irq, NULL, > + pcf85263_irq, > + IRQF_ONESHOT, > + dev->driver->name, pcf85263); > + if (ret) { > + dev_err(dev, "irq %d unavailable (%d)\n", > + pcf85263->irq, ret); > + pcf85263->irq = 0; > + } > + } > + > + if (pcf85263_can_wakeup_machine(pcf85263)) > + device_init_wakeup(dev, true); > + > + pcf85263->rtc = devm_rtc_device_register(dev, dev->driver->name, > + &rtc_ops, THIS_MODULE); > + ret = PTR_ERR_OR_ZERO(pcf85263->rtc); > + if (ret) > + return ret; > + > + /* We cannot support UIE mode if we do not have an IRQ line */ > + if (!pcf85263->irq) > + pcf85263->rtc->uie_unsupported = 1; > + > + dev_info(pcf85263->dev, > + "PCF85263 RTC (irqpin=%s irq=%d)\n", > + pcf85263_irqpin_names[pcf85263->irq_pin], > + pcf85263->irq); > + > + return 0; > +} > + > +static int pcf85263_remove(struct i2c_client *client) > +{ > + struct pcf85263 *pcf85263 = i2c_get_clientdata(client); > + > + if (pcf85263_can_wakeup_machine(pcf85263)) > + device_init_wakeup(pcf85263->dev, false); > + > + return 0; > +} > + > +#ifdef CONFIG_PM_SLEEP > +static int pcf85263_suspend(struct device *dev) > +{ > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > + int ret = 0; > + > + if (device_may_wakeup(dev)) > + ret = enable_irq_wake(pcf85263->irq); > + > + return ret; > +} > + > +static int pcf85263_resume(struct device *dev) > +{ > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > + int ret = 0; > + > + if (device_may_wakeup(dev)) > + ret = disable_irq_wake(pcf85263->irq); > + > + return ret; > +} > + > +#endif > + > +static const struct i2c_device_id pcf85263_id[] = { > + { "pcf85263", 0 }, > + { } > +}; > +MODULE_DEVICE_TABLE(i2c, pcf85263_id); > + > +#ifdef CONFIG_OF > +static const struct of_device_id pcf85263_of_match[] = { > + { .compatible = "nxp,pcf85263" }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, pcf85263_of_match); > +#endif > + > +static SIMPLE_DEV_PM_OPS(pcf85263_pm_ops, pcf85263_suspend, pcf85263_resume); > + > +static struct i2c_driver pcf85263_driver = { > + .driver = { > + .name = "rtc-pcf85263", > + .of_match_table = of_match_ptr(pcf85263_of_match), > + .pm = &pcf85263_pm_ops, > + }, > + .probe = pcf85263_probe, > + .remove = pcf85263_remove, > + .id_table = pcf85263_id, > +}; > + > +module_i2c_driver(pcf85263_driver); > + > +MODULE_AUTHOR("Martin Fuzzey <mfuzzey@parkeon.com>"); > +MODULE_DESCRIPTION("PCF85263 RTC Driver"); > +MODULE_LICENSE("GPL"); >
Hello, I wanted to inform you that we are using this kernel driver on a beaglebone black and we are not having any kind of issue. Thanks for your work Il giorno mercoledì 21 settembre 2016 23:35:42 UTC+2, alexandre.belloni ha scritto: > > Hi, > > Sorry for the very late review. > > The driver seems good to me but I'd like a review from the DT > maintainers before taking it. > > On 01/08/2016 at 17:50:34 +0200, Martin Fuzzey wrote : > > Add basic support for NXP PCF85263 I2C RTC chip. > > > > Only RTC and Alarm functions are supported. > > > > Signed-off-by: Martin Fuzzey <mfu...@parkeon.com <javascript:>> > > --- > > drivers/rtc/Kconfig | 8 + > > drivers/rtc/Makefile | 1 > > drivers/rtc/rtc-pcf85263.c | 655 > ++++++++++++++++++++++++++++++++++++++++++++ > > 3 files changed, 664 insertions(+) > > create mode 100644 drivers/rtc/rtc-pcf85263.c > > > > diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig > > index 18639e0..63ec0fc 100644 > > --- a/drivers/rtc/Kconfig > > +++ b/drivers/rtc/Kconfig > > @@ -396,6 +396,14 @@ config RTC_DRV_PCF85063 > > This driver can also be built as a module. If so, the module > > will be called rtc-pcf85063. > > > > +config RTC_DRV_PCF85263 > > + tristate "NXP PCF85263" > > + help > > + If you say yes here you get support for the PCF85263 RTC chip > > + > > + This driver can also be built as a module. If so, the module > > + will be called rtc-pcf85263. > > + > > config RTC_DRV_PCF8563 > > tristate "Philips PCF8563/Epson RTC8564" > > help > > diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile > > index ea28337..04f48fd 100644 > > --- a/drivers/rtc/Makefile > > +++ b/drivers/rtc/Makefile > > @@ -108,6 +108,7 @@ obj-$(CONFIG_RTC_DRV_PCF2123) += > rtc-pcf2123.o > > obj-$(CONFIG_RTC_DRV_PCF2127) += rtc-pcf2127.o > > obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o > > obj-$(CONFIG_RTC_DRV_PCF85063) += rtc-pcf85063.o > > +obj-$(CONFIG_RTC_DRV_PCF85263) += rtc-pcf85263.o > > obj-$(CONFIG_RTC_DRV_PCF8523) += rtc-pcf8523.o > > obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o > > obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o > > diff --git a/drivers/rtc/rtc-pcf85263.c b/drivers/rtc/rtc-pcf85263.c > > new file mode 100644 > > index 0000000..997742d > > --- /dev/null > > +++ b/drivers/rtc/rtc-pcf85263.c > > @@ -0,0 +1,655 @@ > > +/* > > + * rtc-pcf85263 Driver for the NXP PCF85263 RTC > > + * > > + * Copyright 2016 Parkeon > > + * > > + * This program is free software; you can redistribute it and/or modify > > + * it under the terms of the GNU General Public License version 2 as > > + * published by the Free Software Foundation. > > + */ > > + > > +#include <linux/module.h> > > +#include <linux/mutex.h> > > +#include <linux/rtc.h> > > +#include <linux/i2c.h> > > +#include <linux/bcd.h> > > +#include <linux/of.h> > > +#include <linux/of_device.h> > > +#include <linux/regmap.h> > > + > > +#include <dt-bindings/rtc/nxp,pcf85263.h> > > + > > +#define DRV_NAME "rtc-pcf85263" > > + > > +#define PCF85263_REG_RTC_SC 0x01 /* Seconds */ > > +#define PCF85263_REG_RTC_SC_OS BIT(7) /* > Oscilator stopped flag */ > > + > > +#define PCF85263_REG_RTC_MN 0x02 /* Minutes */ > > +#define PCF85263_REG_RTC_HR 0x03 /* Hours */ > > +#define PCF85263_REG_RTC_DT 0x04 /* Day of month 1-31 */ > > +#define PCF85263_REG_RTC_DW 0x05 /* Day of week 0-6 */ > > +#define PCF85263_REG_RTC_MO 0x06 /* Month 1-12 */ > > +#define PCF85263_REG_RTC_YR 0x07 /* Year 0-99 */ > > + > > +#define PCF85263_REG_ALM1_SC 0x08 /* Seconds */ > > +#define PCF85263_REG_ALM1_MN 0x09 /* Minutes */ > > +#define PCF85263_REG_ALM1_HR 0x0a /* Hours */ > > +#define PCF85263_REG_ALM1_DT 0x0b /* Day of month 1-31 */ > > +#define PCF85263_REG_ALM1_MO 0x0c /* Month 1-12 */ > > + > > +#define PCF85263_REG_ALM_CTL 0x10 > > +#define PCF85263_REG_ALM_CTL_ALL_A1E 0x1f /* sec,min,hr,day,mon > alarm 1 */ > > + > > +#define PCF85263_REG_OSC 0x25 > > +#define PCF85263_REG_OSC_CL_MASK (BIT(0) | BIT(1)) > > +#define PCF85263_REG_OSC_CL_SHIFT 0 > > +#define PCF85263_REG_OSC_OSCD_MASK (BIT(2) | BIT(3)) > > +#define PCF85263_REG_OSC_OSCD_SHIFT 2 > > +#define PCF85263_REG_OSC_LOWJ BIT(4) > > +#define PCF85263_REG_OSC_12H BIT(5) > > + > > +#define PCF85263_REG_PINIO 0x27 > > +#define PCF85263_REG_PINIO_INTAPM_MASK (BIT(0) | BIT(1)) > > +#define PCF85263_REG_PINIO_INTAPM_SHIFT 0 > > +#define PCF85263_INTAPM_INTA (0x2 << > PCF85263_REG_PINIO_INTAPM_SHIFT) > > +#define PCF85263_INTAPM_HIGHZ (0x3 << > PCF85263_REG_PINIO_INTAPM_SHIFT) > > +#define PCF85263_REG_PINIO_TSPM_MASK (BIT(2) | BIT(3)) > > +#define PCF85263_REG_PINIO_TSPM_SHIFT 2 > > +#define PCF85263_TSPM_DISABLED (0x0 << > PCF85263_REG_PINIO_TSPM_SHIFT) > > +#define PCF85263_TSPM_INTB (0x1 << > PCF85263_REG_PINIO_TSPM_SHIFT) > > +#define PCF85263_REG_PINIO_CLKDISABLE BIT(7) > > + > > +#define PCF85263_REG_FUNCTION 0x28 > > +#define PCF85263_REG_FUNCTION_COF_MASK 0x7 > > +#define PCF85263_REG_FUNCTION_COF_OFF 0x7 /* No clock > output */ > > + > > +#define PCF85263_REG_INTA_CTL 0x29 > > +#define PCF85263_REG_INTB_CTL 0x2A > > +#define PCF85263_REG_INTx_CTL_A1E BIT(4) /* Alarm 1 */ > > +#define PCF85263_REG_INTx_CTL_ILP BIT(7) /* 0=pulse, > 1=level */ > > + > > +#define PCF85263_REG_FLAGS 0x2B > > +#define PCF85263_REG_FLAGS_A1F BIT(5) > > + > > +#define PCF85263_REG_RAM_BYTE 0x2c > > + > > +#define PCF85263_REG_STOPENABLE 0x2e > > +#define PCF85263_REG_STOPENABLE_STOP BIT(0) > > + > > +#define PCF85263_REG_RESET 0x2f /* Reset command */ > > +#define PCF85263_REG_RESET_CMD_CPR 0xa4 /* Clear > prescaler */ > > + > > +#define PCF85263_MAX_REG 0x2f > > + > > +#define PCF85263_HR_PM BIT(5) > > + > > +enum pcf85263_irqpin { > > + PCF85263_IRQPIN_NONE, > > + PCF85263_IRQPIN_INTA, > > + PCF85263_IRQPIN_INTB > > +}; > > + > > +static const char *const pcf85263_irqpin_names[] = { > > + [PCF85263_IRQPIN_NONE] = "None", > > + [PCF85263_IRQPIN_INTA] = "INTA", > > + [PCF85263_IRQPIN_INTB] = "INTB" > > +}; > > + > > +struct pcf85263 { > > + struct device *dev; > > + struct rtc_device *rtc; > > + struct regmap *regmap; > > + enum pcf85263_irqpin irq_pin; > > + int irq; > > + bool mode_12h; > > +}; > > + > > +/* > > + * Helpers to convert 12h to 24h and vice versa. > > + * Values in register are stored in BCD with a PM flag in bit 5 > > + * > > + * 23:00 <=> 11PM <=> 0x31 > > + * 00:00 <=> 12AM <=> 0x12 > > + * 01:00 <=> 1AM <=> 0x01 > > + * 12:00 <=> 12PM <=> 0x32 > > + * 13:00 <=> 1PM <=> 0x21 > > + */ > > +static int pcf85263_bcd12h_to_bin24h(int regval) > > +{ > > + int hr = bcd2bin(regval & 0x1f); > > + bool pm = regval & PCF85263_HR_PM; > > + > > + if (hr == 12) > > + return pm ? 12 : 0; > > + > > + return pm ? hr + 12 : hr; > > +} > > + > > +static int pcf85263_bin24h_to_bcd12h(int hr24) > > +{ > > + bool pm = hr24 >= 12; > > + int hr12 = hr24 % 12; > > + > > + if (!hr12) > > + hr12++; > > + > > + return bin2bcd(hr12) | pm ? 0 : PCF85263_HR_PM; > > +} > > + > > +static int pcf85263_read_time(struct device *dev, struct rtc_time *tm) > > +{ > > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > > + const int first = PCF85263_REG_RTC_SC; > > + const int last = PCF85263_REG_RTC_YR; > > + const int len = last - first + 1; > > + u8 regs[len]; > > + u8 hr_reg; > > + int ret; > > + > > + ret = regmap_bulk_read(pcf85263->regmap, first, regs, len); > > + if (ret) > > + return ret; > > + > > + if (regs[PCF85263_REG_RTC_SC - first] & PCF85263_REG_RTC_SC_OS) > { > > + dev_warn(dev, "Oscillator stop detected, date/time is > not reliable.\n"); > > + return -EINVAL; > > + } > > + > > + tm->tm_sec = bcd2bin(regs[PCF85263_REG_RTC_SC - first] & 0x7f); > > + tm->tm_min = bcd2bin(regs[PCF85263_REG_RTC_MN - first] & 0x7f); > > + > > + hr_reg = regs[PCF85263_REG_RTC_HR - first]; > > + if (pcf85263->mode_12h) > > + tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg); > > + else > > + tm->tm_hour = bcd2bin(hr_reg & 0x3f); > > + > > + tm->tm_mday = bcd2bin(regs[PCF85263_REG_RTC_DT - first]); > > + tm->tm_wday = bcd2bin(regs[PCF85263_REG_RTC_DW - first]); > > + tm->tm_mon = bcd2bin(regs[PCF85263_REG_RTC_MO - first]) - 1; > > + tm->tm_year = bcd2bin(regs[PCF85263_REG_RTC_YR - first]); > > + > > + tm->tm_year += 100; /* Assume 21st century */ > > + > > + return 0; > > +} > > + > > +static int pcf85263_set_time(struct device *dev, struct rtc_time *tm) > > +{ > > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > > + > > + /* > > + * Before setting time need to stop RTC and disable prescaler > > + * Do this all in a single I2C transaction exploiting > wraparound > > + * as described in data sheet. > > + * This means that the array below must be in register order > > + */ > > + u8 regs[] = { > > + PCF85263_REG_STOPENABLE_STOP, /* STOP */ > > + PCF85263_REG_RESET_CMD_CPR, /* Disable prescaler > */ > > + /* Wrap around to register 0 (1/100s) */ > > + 0, /* 1/100s always > zero. */ > > + bin2bcd(tm->tm_sec), > > + bin2bcd(tm->tm_min), > > + bin2bcd(tm->tm_hour), /* 24-hour */ > > + bin2bcd(tm->tm_mday), > > + bin2bcd(tm->tm_wday + 1), > > + bin2bcd(tm->tm_mon + 1), > > + bin2bcd(tm->tm_year % 100) > > + }; > > + int ret; > > + > > + ret = regmap_bulk_write(pcf85263->regmap, > PCF85263_REG_STOPENABLE, > > + regs, sizeof(regs)); > > + if (ret) > > + return ret; > > + > > + /* As we have set the time in 24H update the hardware for that > */ > > + if (pcf85263->mode_12h) { > > + pcf85263->mode_12h = false; > > + ret = regmap_update_bits(pcf85263->regmap, > PCF85263_REG_OSC, > > + PCF85263_REG_OSC_12H, 0); > > + if (ret) > > + return ret; > > + } > > + > > + /* Start it again */ > > + return regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, > 0); > > +} > > + > > +static int pcf85263_enable_alarm(struct pcf85263 *pcf85263, bool > enable) > > +{ > > + int reg; > > + int ret; > > + > > + ret = regmap_update_bits(pcf85263->regmap, > PCF85263_REG_ALM_CTL, > > + PCF85263_REG_ALM_CTL_ALL_A1E, > > + enable ? PCF85263_REG_ALM_CTL_ALL_A1E > : 0); > > + if (ret) > > + return ret; > > + > > + switch (pcf85263->irq_pin) { > > + case PCF85263_IRQPIN_NONE: > > + return 0; > > + > > + case PCF85263_IRQPIN_INTA: > > + reg = PCF85263_REG_INTA_CTL; > > + break; > > + > > + case PCF85263_IRQPIN_INTB: > > + reg = PCF85263_REG_INTB_CTL; > > + break; > > + > > + default: > > + return -EINVAL; > > + } > > + > > + return regmap_update_bits(pcf85263->regmap, reg, > > + PCF85263_REG_INTx_CTL_A1E, > > + enable ? PCF85263_REG_INTx_CTL_A1E : > 0); > > +} > > + > > +static int pcf85263_read_alarm(struct device *dev, struct rtc_wkalrm > *alarm) > > +{ > > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > > + struct rtc_time *tm = &alarm->time; > > + const int first = PCF85263_REG_ALM1_SC; > > + const int last = PCF85263_REG_ALM1_MO; > > + const int len = last - first + 1; > > + u8 regs[len]; > > + u8 hr_reg; > > + unsigned int regval; > > + int ret; > > + > > + ret = regmap_bulk_read(pcf85263->regmap, first, regs, len); > > + if (ret) > > + return ret; > > + > > + tm->tm_sec = bcd2bin(regs[PCF85263_REG_ALM1_SC - first] & > 0x7f); > > + tm->tm_min = bcd2bin(regs[PCF85263_REG_ALM1_MN - first] & > 0x7f); > > + > > + hr_reg = regs[PCF85263_REG_ALM1_HR - first]; > > + if (pcf85263->mode_12h) > > + tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg); > > + else > > + tm->tm_hour = bcd2bin(hr_reg & 0x3f); > > + > > + tm->tm_mday = bcd2bin(regs[PCF85263_REG_ALM1_DT - first]); > > + tm->tm_mon = bcd2bin(regs[PCF85263_REG_ALM1_MO - first]) - 1; > > + tm->tm_year = -1; > > + tm->tm_wday = -1; > > + > > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_ALM_CTL, > ®val); > > + if (ret) > > + return ret; > > + alarm->enabled = !!(regval & PCF85263_REG_ALM_CTL_ALL_A1E); > > + > > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS, > ®val); > > + if (ret) > > + return ret; > > + alarm->pending = !!(regval & PCF85263_REG_FLAGS_A1F); > > + > > + return 0; > > +} > > + > > +static int pcf85263_set_alarm(struct device *dev, struct rtc_wkalrm > *alarm) > > +{ > > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > > + struct rtc_time *tm = &alarm->time; > > + const int first = PCF85263_REG_ALM1_SC; > > + const int last = PCF85263_REG_ALM1_MO; > > + const int len = last - first + 1; > > + u8 regs[len]; > > + int ret; > > + > > + /* Disable alarm comparison during update */ > > + ret = pcf85263_enable_alarm(pcf85263, false); > > + if (ret) > > + return ret; > > + > > + /* Clear any pending alarm (write 0=>clr, 1=>no change) */ > > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS, > > + ~PCF85263_REG_FLAGS_A1F); > > + if (ret) > > + return ret; > > + > > + /* Set the alarm time registers */ > > + regs[PCF85263_REG_ALM1_SC - first] = bin2bcd(tm->tm_sec); > > + regs[PCF85263_REG_ALM1_MN - first] = bin2bcd(tm->tm_min); > > + regs[PCF85263_REG_ALM1_HR - first] = pcf85263->mode_12h ? > > + pcf85263_bin24h_to_bcd12h(tm->tm_hour) : > > + bin2bcd(tm->tm_hour); > > + regs[PCF85263_REG_ALM1_DT - first] = bin2bcd(tm->tm_mday); > > + regs[PCF85263_REG_ALM1_MO - first] = bin2bcd(tm->tm_mon + 1); > > + > > + ret = regmap_bulk_write(pcf85263->regmap, first, regs, > sizeof(regs)); > > + if (ret) > > + return ret; > > + > > + if (alarm->enabled) > > + ret = pcf85263_enable_alarm(pcf85263, true); > > + > > + return ret; > > +} > > + > > +static int pcf85263_alarm_irq_enable(struct device *dev, unsigned int > enable) > > +{ > > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > > + > > + return pcf85263_enable_alarm(pcf85263, !!enable); > > +} > > + > > +static irqreturn_t pcf85263_irq(int irq, void *data) > > +{ > > + struct pcf85263 *pcf85263 = data; > > + unsigned int regval; > > + int ret; > > + > > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS, > ®val); > > + if (ret) > > + return IRQ_NONE; > > + > > + if (regval & PCF85263_REG_FLAGS_A1F) { > > + regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS, > > + ~PCF85263_REG_FLAGS_A1F); > > + > > + rtc_update_irq(pcf85263->rtc, 1, RTC_IRQF | RTC_AF); > > + > > + return IRQ_HANDLED; > > + } > > + > > + return IRQ_NONE; > > +} > > + > > +static int pcf85263_check_osc_stopped(struct pcf85263 *pcf85263) > > +{ > > + unsigned int regval; > > + int ret; > > + > > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_RTC_SC, > ®val); > > + if (ret) > > + return ret; > > + > > + ret = regval & PCF85263_REG_RTC_SC_OS ? 1 : 0; > > + if (ret) > > + dev_warn(pcf85263->dev, "Oscillator stop detected, > date/time is not reliable.\n"); > > + > > + return ret; > > +} > > + > > +#ifdef CONFIG_RTC_INTF_DEV > > +static int pcf85263_ioctl(struct device *dev, > > + unsigned int cmd, unsigned long arg) > > +{ > > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > > + int ret; > > + > > + switch (cmd) { > > + case RTC_VL_READ: > > + ret = pcf85263_check_osc_stopped(pcf85263); > > + if (ret < 0) > > + return ret; > > + > > + if (copy_to_user((void __user *)arg, &ret, > sizeof(int))) > > + return -EFAULT; > > + return 0; > > + > > + case RTC_VL_CLR: > > + return regmap_update_bits(pcf85263->regmap, > > + PCF85263_REG_RTC_SC, > > + PCF85263_REG_RTC_SC_OS, 0); > > + default: > > + return -ENOIOCTLCMD; > > + } > > +} > > +#else > > +#define pcf85263_ioctl NULL > > +#endif > > + > > +static int pcf85263_init_hw(struct pcf85263 *pcf85263) > > +{ > > + struct device_node *np = pcf85263->dev->of_node; > > + unsigned int regval; > > + u32 propval; > > + int ret; > > + > > + /* Determine if oscilator has been stopped (probably low power) > */ > > + ret = pcf85263_check_osc_stopped(pcf85263); > > + if (ret < 0) { > > + /* Log here since this is the first hw access on probe > */ > > + dev_err(pcf85263->dev, "Unable to read register\n"); > > + > > + return ret; > > + } > > + > > + /* Determine 12/24H mode */ > > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_OSC, ®val); > > + if (ret) > > + return ret; > > + pcf85263->mode_12h = !!(regval & PCF85263_REG_OSC_12H); > > + > > + /* Set oscilator register */ > > + regval &= ~PCF85263_REG_OSC_12H; /* keep current 12/24 h > setting */ > > + > > + propval = PCF85263_QUARTZCAP_12p5pF; > > + of_property_read_u32(np, "quartz-load-capacitance", &propval); > > + regval |= ((propval << PCF85263_REG_OSC_CL_SHIFT) > > + & PCF85263_REG_OSC_CL_MASK); > > + > > + propval = PCF85263_QUARTZDRIVE_NORMAL; > > + of_property_read_u32(np, "quartz-drive-strength", &propval); > > + regval |= ((propval << PCF85263_REG_OSC_OSCD_SHIFT) > > + & PCF85263_REG_OSC_OSCD_MASK); > > + > > + if (of_property_read_bool(np, "quartz-low-jitter")) > > + regval |= PCF85263_REG_OSC_LOWJ; > > + > > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_OSC, regval); > > + if (ret) > > + return ret; > > + > > + /* Set function register (RTC mode, 1s tick, clock output > static) */ > > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_FUNCTION, > > + PCF85263_REG_FUNCTION_COF_OFF); > > + if (ret) > > + return ret; > > + > > + /* Set all interrupts to disabled, level mode */ > > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTA_CTL, > > + PCF85263_REG_INTx_CTL_ILP); > > + if (ret) > > + return ret; > > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTB_CTL, > > + PCF85263_REG_INTx_CTL_ILP); > > + if (ret) > > + return ret; > > + > > + /* Setup IO pin config register */ > > + regval = PCF85263_REG_PINIO_CLKDISABLE; > > + switch (pcf85263->irq_pin) { > > + case PCF85263_IRQPIN_INTA: > > + regval |= (PCF85263_INTAPM_INTA | > PCF85263_TSPM_DISABLED); > > + break; > > + case PCF85263_IRQPIN_INTB: > > + regval |= (PCF85263_INTAPM_HIGHZ | PCF85263_TSPM_INTB); > > + break; > > + case PCF85263_IRQPIN_NONE: > > + regval |= (PCF85263_INTAPM_HIGHZ | > PCF85263_TSPM_DISABLED); > > + break; > > + } > > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_PINIO, > regval); > > + > > + return ret; > > +} > > + > > +static const struct rtc_class_ops rtc_ops = { > > + .ioctl = pcf85263_ioctl, > > + .read_time = pcf85263_read_time, > > + .set_time = pcf85263_set_time, > > + .read_alarm = pcf85263_read_alarm, > > + .set_alarm = pcf85263_set_alarm, > > + .alarm_irq_enable = pcf85263_alarm_irq_enable, > > +}; > > + > > +static const struct regmap_config pcf85263_regmap_cfg = { > > + .reg_bits = 8, > > + .val_bits = 8, > > + .max_register = PCF85263_MAX_REG, > > +}; > > + > > +/* > > + * On some boards the interrupt line may not be wired to the CPU but > only to > > + * a power supply circuit. > > + * In that case no interrupt will be specified in the device tree but > the > > + * wakeup-source DT property may be used to enable wakeup programming > in > > + * sysfs > > + */ > > +static bool pcf85263_can_wakeup_machine(struct pcf85263 *pcf85263) > > +{ > > + return pcf85263->irq || > > + of_property_read_bool(pcf85263->dev->of_node, > "wakeup-source"); > > +} > > + > > +static int pcf85263_probe(struct i2c_client *client, > > + const struct i2c_device_id *id) > > +{ > > + struct device *dev = &client->dev; > > + struct pcf85263 *pcf85263; > > + int ret; > > + > > + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | > > + I2C_FUNC_SMBUS_BYTE_DATA | > > + I2C_FUNC_SMBUS_I2C_BLOCK)) > > + return -ENODEV; > > + > > + pcf85263 = devm_kzalloc(dev, sizeof(*pcf85263), GFP_KERNEL); > > + if (!pcf85263) > > + return -ENOMEM; > > + > > + pcf85263->dev = dev; > > + pcf85263->irq = client->irq; > > + dev_set_drvdata(dev, pcf85263); > > + > > + pcf85263->regmap = devm_regmap_init_i2c(client, > &pcf85263_regmap_cfg); > > + if (IS_ERR(pcf85263->regmap)) { > > + ret = PTR_ERR(pcf85263->regmap); > > + dev_err(dev, "regmap allocation failed (%d)\n", ret); > > + > > + return ret; > > + } > > + > > + /* Determine which interrupt pin the board uses */ > > + if (pcf85263_can_wakeup_machine(pcf85263)) { > > + if (of_property_match_string(dev->of_node, > > + "interrupt-names", "INTB") > >= 0) > > + pcf85263->irq_pin = PCF85263_IRQPIN_INTB; > > + else > > + pcf85263->irq_pin = PCF85263_IRQPIN_INTA; > > + } else { > > + pcf85263->irq_pin = PCF85263_IRQPIN_NONE; > > + } > > + > > + ret = pcf85263_init_hw(pcf85263); > > + if (ret) > > + return ret; > > + > > + if (pcf85263->irq) { > > + ret = devm_request_threaded_irq(dev, pcf85263->irq, > NULL, > > + pcf85263_irq, > > + IRQF_ONESHOT, > > + dev->driver->name, > pcf85263); > > + if (ret) { > > + dev_err(dev, "irq %d unavailable (%d)\n", > > + pcf85263->irq, ret); > > + pcf85263->irq = 0; > > + } > > + } > > + > > + if (pcf85263_can_wakeup_machine(pcf85263)) > > + device_init_wakeup(dev, true); > > + > > + pcf85263->rtc = devm_rtc_device_register(dev, > dev->driver->name, > > + &rtc_ops, > THIS_MODULE); > > + ret = PTR_ERR_OR_ZERO(pcf85263->rtc); > > + if (ret) > > + return ret; > > + > > + /* We cannot support UIE mode if we do not have an IRQ line */ > > + if (!pcf85263->irq) > > + pcf85263->rtc->uie_unsupported = 1; > > + > > + dev_info(pcf85263->dev, > > + "PCF85263 RTC (irqpin=%s irq=%d)\n", > > + pcf85263_irqpin_names[pcf85263->irq_pin], > > + pcf85263->irq); > > + > > + return 0; > > +} > > + > > +static int pcf85263_remove(struct i2c_client *client) > > +{ > > + struct pcf85263 *pcf85263 = i2c_get_clientdata(client); > > + > > + if (pcf85263_can_wakeup_machine(pcf85263)) > > + device_init_wakeup(pcf85263->dev, false); > > + > > + return 0; > > +} > > + > > +#ifdef CONFIG_PM_SLEEP > > +static int pcf85263_suspend(struct device *dev) > > +{ > > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > > + int ret = 0; > > + > > + if (device_may_wakeup(dev)) > > + ret = enable_irq_wake(pcf85263->irq); > > + > > + return ret; > > +} > > + > > +static int pcf85263_resume(struct device *dev) > > +{ > > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); > > + int ret = 0; > > + > > + if (device_may_wakeup(dev)) > > + ret = disable_irq_wake(pcf85263->irq); > > + > > + return ret; > > +} > > + > > +#endif > > + > > +static const struct i2c_device_id pcf85263_id[] = { > > + { "pcf85263", 0 }, > > + { } > > +}; > > +MODULE_DEVICE_TABLE(i2c, pcf85263_id); > > + > > +#ifdef CONFIG_OF > > +static const struct of_device_id pcf85263_of_match[] = { > > + { .compatible = "nxp,pcf85263" }, > > + {} > > +}; > > +MODULE_DEVICE_TABLE(of, pcf85263_of_match); > > +#endif > > + > > +static SIMPLE_DEV_PM_OPS(pcf85263_pm_ops, pcf85263_suspend, > pcf85263_resume); > > + > > +static struct i2c_driver pcf85263_driver = { > > + .driver = { > > + .name = "rtc-pcf85263", > > + .of_match_table = of_match_ptr(pcf85263_of_match), > > + .pm = &pcf85263_pm_ops, > > + }, > > + .probe = pcf85263_probe, > > + .remove = pcf85263_remove, > > + .id_table = pcf85263_id, > > +}; > > + > > +module_i2c_driver(pcf85263_driver); > > + > > +MODULE_AUTHOR("Martin Fuzzey <mfu...@parkeon.com <javascript:>>"); > > +MODULE_DESCRIPTION("PCF85263 RTC Driver"); > > +MODULE_LICENSE("GPL"); > > > > -- > Alexandre Belloni, Free Electrons > Embedded Linux and Kernel engineering > http://free-electrons.com >
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 18639e0..63ec0fc 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -396,6 +396,14 @@ config RTC_DRV_PCF85063 This driver can also be built as a module. If so, the module will be called rtc-pcf85063. +config RTC_DRV_PCF85263 + tristate "NXP PCF85263" + help + If you say yes here you get support for the PCF85263 RTC chip + + This driver can also be built as a module. If so, the module + will be called rtc-pcf85263. + config RTC_DRV_PCF8563 tristate "Philips PCF8563/Epson RTC8564" help diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index ea28337..04f48fd 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -108,6 +108,7 @@ obj-$(CONFIG_RTC_DRV_PCF2123) += rtc-pcf2123.o obj-$(CONFIG_RTC_DRV_PCF2127) += rtc-pcf2127.o obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o obj-$(CONFIG_RTC_DRV_PCF85063) += rtc-pcf85063.o +obj-$(CONFIG_RTC_DRV_PCF85263) += rtc-pcf85263.o obj-$(CONFIG_RTC_DRV_PCF8523) += rtc-pcf8523.o obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o diff --git a/drivers/rtc/rtc-pcf85263.c b/drivers/rtc/rtc-pcf85263.c new file mode 100644 index 0000000..997742d --- /dev/null +++ b/drivers/rtc/rtc-pcf85263.c @@ -0,0 +1,655 @@ +/* + * rtc-pcf85263 Driver for the NXP PCF85263 RTC + * + * Copyright 2016 Parkeon + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/rtc.h> +#include <linux/i2c.h> +#include <linux/bcd.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> + +#include <dt-bindings/rtc/nxp,pcf85263.h> + +#define DRV_NAME "rtc-pcf85263" + +#define PCF85263_REG_RTC_SC 0x01 /* Seconds */ +#define PCF85263_REG_RTC_SC_OS BIT(7) /* Oscilator stopped flag */ + +#define PCF85263_REG_RTC_MN 0x02 /* Minutes */ +#define PCF85263_REG_RTC_HR 0x03 /* Hours */ +#define PCF85263_REG_RTC_DT 0x04 /* Day of month 1-31 */ +#define PCF85263_REG_RTC_DW 0x05 /* Day of week 0-6 */ +#define PCF85263_REG_RTC_MO 0x06 /* Month 1-12 */ +#define PCF85263_REG_RTC_YR 0x07 /* Year 0-99 */ + +#define PCF85263_REG_ALM1_SC 0x08 /* Seconds */ +#define PCF85263_REG_ALM1_MN 0x09 /* Minutes */ +#define PCF85263_REG_ALM1_HR 0x0a /* Hours */ +#define PCF85263_REG_ALM1_DT 0x0b /* Day of month 1-31 */ +#define PCF85263_REG_ALM1_MO 0x0c /* Month 1-12 */ + +#define PCF85263_REG_ALM_CTL 0x10 +#define PCF85263_REG_ALM_CTL_ALL_A1E 0x1f /* sec,min,hr,day,mon alarm 1 */ + +#define PCF85263_REG_OSC 0x25 +#define PCF85263_REG_OSC_CL_MASK (BIT(0) | BIT(1)) +#define PCF85263_REG_OSC_CL_SHIFT 0 +#define PCF85263_REG_OSC_OSCD_MASK (BIT(2) | BIT(3)) +#define PCF85263_REG_OSC_OSCD_SHIFT 2 +#define PCF85263_REG_OSC_LOWJ BIT(4) +#define PCF85263_REG_OSC_12H BIT(5) + +#define PCF85263_REG_PINIO 0x27 +#define PCF85263_REG_PINIO_INTAPM_MASK (BIT(0) | BIT(1)) +#define PCF85263_REG_PINIO_INTAPM_SHIFT 0 +#define PCF85263_INTAPM_INTA (0x2 << PCF85263_REG_PINIO_INTAPM_SHIFT) +#define PCF85263_INTAPM_HIGHZ (0x3 << PCF85263_REG_PINIO_INTAPM_SHIFT) +#define PCF85263_REG_PINIO_TSPM_MASK (BIT(2) | BIT(3)) +#define PCF85263_REG_PINIO_TSPM_SHIFT 2 +#define PCF85263_TSPM_DISABLED (0x0 << PCF85263_REG_PINIO_TSPM_SHIFT) +#define PCF85263_TSPM_INTB (0x1 << PCF85263_REG_PINIO_TSPM_SHIFT) +#define PCF85263_REG_PINIO_CLKDISABLE BIT(7) + +#define PCF85263_REG_FUNCTION 0x28 +#define PCF85263_REG_FUNCTION_COF_MASK 0x7 +#define PCF85263_REG_FUNCTION_COF_OFF 0x7 /* No clock output */ + +#define PCF85263_REG_INTA_CTL 0x29 +#define PCF85263_REG_INTB_CTL 0x2A +#define PCF85263_REG_INTx_CTL_A1E BIT(4) /* Alarm 1 */ +#define PCF85263_REG_INTx_CTL_ILP BIT(7) /* 0=pulse, 1=level */ + +#define PCF85263_REG_FLAGS 0x2B +#define PCF85263_REG_FLAGS_A1F BIT(5) + +#define PCF85263_REG_RAM_BYTE 0x2c + +#define PCF85263_REG_STOPENABLE 0x2e +#define PCF85263_REG_STOPENABLE_STOP BIT(0) + +#define PCF85263_REG_RESET 0x2f /* Reset command */ +#define PCF85263_REG_RESET_CMD_CPR 0xa4 /* Clear prescaler */ + +#define PCF85263_MAX_REG 0x2f + +#define PCF85263_HR_PM BIT(5) + +enum pcf85263_irqpin { + PCF85263_IRQPIN_NONE, + PCF85263_IRQPIN_INTA, + PCF85263_IRQPIN_INTB +}; + +static const char *const pcf85263_irqpin_names[] = { + [PCF85263_IRQPIN_NONE] = "None", + [PCF85263_IRQPIN_INTA] = "INTA", + [PCF85263_IRQPIN_INTB] = "INTB" +}; + +struct pcf85263 { + struct device *dev; + struct rtc_device *rtc; + struct regmap *regmap; + enum pcf85263_irqpin irq_pin; + int irq; + bool mode_12h; +}; + +/* + * Helpers to convert 12h to 24h and vice versa. + * Values in register are stored in BCD with a PM flag in bit 5 + * + * 23:00 <=> 11PM <=> 0x31 + * 00:00 <=> 12AM <=> 0x12 + * 01:00 <=> 1AM <=> 0x01 + * 12:00 <=> 12PM <=> 0x32 + * 13:00 <=> 1PM <=> 0x21 + */ +static int pcf85263_bcd12h_to_bin24h(int regval) +{ + int hr = bcd2bin(regval & 0x1f); + bool pm = regval & PCF85263_HR_PM; + + if (hr == 12) + return pm ? 12 : 0; + + return pm ? hr + 12 : hr; +} + +static int pcf85263_bin24h_to_bcd12h(int hr24) +{ + bool pm = hr24 >= 12; + int hr12 = hr24 % 12; + + if (!hr12) + hr12++; + + return bin2bcd(hr12) | pm ? 0 : PCF85263_HR_PM; +} + +static int pcf85263_read_time(struct device *dev, struct rtc_time *tm) +{ + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); + const int first = PCF85263_REG_RTC_SC; + const int last = PCF85263_REG_RTC_YR; + const int len = last - first + 1; + u8 regs[len]; + u8 hr_reg; + int ret; + + ret = regmap_bulk_read(pcf85263->regmap, first, regs, len); + if (ret) + return ret; + + if (regs[PCF85263_REG_RTC_SC - first] & PCF85263_REG_RTC_SC_OS) { + dev_warn(dev, "Oscillator stop detected, date/time is not reliable.\n"); + return -EINVAL; + } + + tm->tm_sec = bcd2bin(regs[PCF85263_REG_RTC_SC - first] & 0x7f); + tm->tm_min = bcd2bin(regs[PCF85263_REG_RTC_MN - first] & 0x7f); + + hr_reg = regs[PCF85263_REG_RTC_HR - first]; + if (pcf85263->mode_12h) + tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg); + else + tm->tm_hour = bcd2bin(hr_reg & 0x3f); + + tm->tm_mday = bcd2bin(regs[PCF85263_REG_RTC_DT - first]); + tm->tm_wday = bcd2bin(regs[PCF85263_REG_RTC_DW - first]); + tm->tm_mon = bcd2bin(regs[PCF85263_REG_RTC_MO - first]) - 1; + tm->tm_year = bcd2bin(regs[PCF85263_REG_RTC_YR - first]); + + tm->tm_year += 100; /* Assume 21st century */ + + return 0; +} + +static int pcf85263_set_time(struct device *dev, struct rtc_time *tm) +{ + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); + + /* + * Before setting time need to stop RTC and disable prescaler + * Do this all in a single I2C transaction exploiting wraparound + * as described in data sheet. + * This means that the array below must be in register order + */ + u8 regs[] = { + PCF85263_REG_STOPENABLE_STOP, /* STOP */ + PCF85263_REG_RESET_CMD_CPR, /* Disable prescaler */ + /* Wrap around to register 0 (1/100s) */ + 0, /* 1/100s always zero. */ + bin2bcd(tm->tm_sec), + bin2bcd(tm->tm_min), + bin2bcd(tm->tm_hour), /* 24-hour */ + bin2bcd(tm->tm_mday), + bin2bcd(tm->tm_wday + 1), + bin2bcd(tm->tm_mon + 1), + bin2bcd(tm->tm_year % 100) + }; + int ret; + + ret = regmap_bulk_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, + regs, sizeof(regs)); + if (ret) + return ret; + + /* As we have set the time in 24H update the hardware for that */ + if (pcf85263->mode_12h) { + pcf85263->mode_12h = false; + ret = regmap_update_bits(pcf85263->regmap, PCF85263_REG_OSC, + PCF85263_REG_OSC_12H, 0); + if (ret) + return ret; + } + + /* Start it again */ + return regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, 0); +} + +static int pcf85263_enable_alarm(struct pcf85263 *pcf85263, bool enable) +{ + int reg; + int ret; + + ret = regmap_update_bits(pcf85263->regmap, PCF85263_REG_ALM_CTL, + PCF85263_REG_ALM_CTL_ALL_A1E, + enable ? PCF85263_REG_ALM_CTL_ALL_A1E : 0); + if (ret) + return ret; + + switch (pcf85263->irq_pin) { + case PCF85263_IRQPIN_NONE: + return 0; + + case PCF85263_IRQPIN_INTA: + reg = PCF85263_REG_INTA_CTL; + break; + + case PCF85263_IRQPIN_INTB: + reg = PCF85263_REG_INTB_CTL; + break; + + default: + return -EINVAL; + } + + return regmap_update_bits(pcf85263->regmap, reg, + PCF85263_REG_INTx_CTL_A1E, + enable ? PCF85263_REG_INTx_CTL_A1E : 0); +} + +static int pcf85263_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); + struct rtc_time *tm = &alarm->time; + const int first = PCF85263_REG_ALM1_SC; + const int last = PCF85263_REG_ALM1_MO; + const int len = last - first + 1; + u8 regs[len]; + u8 hr_reg; + unsigned int regval; + int ret; + + ret = regmap_bulk_read(pcf85263->regmap, first, regs, len); + if (ret) + return ret; + + tm->tm_sec = bcd2bin(regs[PCF85263_REG_ALM1_SC - first] & 0x7f); + tm->tm_min = bcd2bin(regs[PCF85263_REG_ALM1_MN - first] & 0x7f); + + hr_reg = regs[PCF85263_REG_ALM1_HR - first]; + if (pcf85263->mode_12h) + tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg); + else + tm->tm_hour = bcd2bin(hr_reg & 0x3f); + + tm->tm_mday = bcd2bin(regs[PCF85263_REG_ALM1_DT - first]); + tm->tm_mon = bcd2bin(regs[PCF85263_REG_ALM1_MO - first]) - 1; + tm->tm_year = -1; + tm->tm_wday = -1; + + ret = regmap_read(pcf85263->regmap, PCF85263_REG_ALM_CTL, ®val); + if (ret) + return ret; + alarm->enabled = !!(regval & PCF85263_REG_ALM_CTL_ALL_A1E); + + ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS, ®val); + if (ret) + return ret; + alarm->pending = !!(regval & PCF85263_REG_FLAGS_A1F); + + return 0; +} + +static int pcf85263_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); + struct rtc_time *tm = &alarm->time; + const int first = PCF85263_REG_ALM1_SC; + const int last = PCF85263_REG_ALM1_MO; + const int len = last - first + 1; + u8 regs[len]; + int ret; + + /* Disable alarm comparison during update */ + ret = pcf85263_enable_alarm(pcf85263, false); + if (ret) + return ret; + + /* Clear any pending alarm (write 0=>clr, 1=>no change) */ + ret = regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS, + ~PCF85263_REG_FLAGS_A1F); + if (ret) + return ret; + + /* Set the alarm time registers */ + regs[PCF85263_REG_ALM1_SC - first] = bin2bcd(tm->tm_sec); + regs[PCF85263_REG_ALM1_MN - first] = bin2bcd(tm->tm_min); + regs[PCF85263_REG_ALM1_HR - first] = pcf85263->mode_12h ? + pcf85263_bin24h_to_bcd12h(tm->tm_hour) : + bin2bcd(tm->tm_hour); + regs[PCF85263_REG_ALM1_DT - first] = bin2bcd(tm->tm_mday); + regs[PCF85263_REG_ALM1_MO - first] = bin2bcd(tm->tm_mon + 1); + + ret = regmap_bulk_write(pcf85263->regmap, first, regs, sizeof(regs)); + if (ret) + return ret; + + if (alarm->enabled) + ret = pcf85263_enable_alarm(pcf85263, true); + + return ret; +} + +static int pcf85263_alarm_irq_enable(struct device *dev, unsigned int enable) +{ + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); + + return pcf85263_enable_alarm(pcf85263, !!enable); +} + +static irqreturn_t pcf85263_irq(int irq, void *data) +{ + struct pcf85263 *pcf85263 = data; + unsigned int regval; + int ret; + + ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS, ®val); + if (ret) + return IRQ_NONE; + + if (regval & PCF85263_REG_FLAGS_A1F) { + regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS, + ~PCF85263_REG_FLAGS_A1F); + + rtc_update_irq(pcf85263->rtc, 1, RTC_IRQF | RTC_AF); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int pcf85263_check_osc_stopped(struct pcf85263 *pcf85263) +{ + unsigned int regval; + int ret; + + ret = regmap_read(pcf85263->regmap, PCF85263_REG_RTC_SC, ®val); + if (ret) + return ret; + + ret = regval & PCF85263_REG_RTC_SC_OS ? 1 : 0; + if (ret) + dev_warn(pcf85263->dev, "Oscillator stop detected, date/time is not reliable.\n"); + + return ret; +} + +#ifdef CONFIG_RTC_INTF_DEV +static int pcf85263_ioctl(struct device *dev, + unsigned int cmd, unsigned long arg) +{ + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); + int ret; + + switch (cmd) { + case RTC_VL_READ: + ret = pcf85263_check_osc_stopped(pcf85263); + if (ret < 0) + return ret; + + if (copy_to_user((void __user *)arg, &ret, sizeof(int))) + return -EFAULT; + return 0; + + case RTC_VL_CLR: + return regmap_update_bits(pcf85263->regmap, + PCF85263_REG_RTC_SC, + PCF85263_REG_RTC_SC_OS, 0); + default: + return -ENOIOCTLCMD; + } +} +#else +#define pcf85263_ioctl NULL +#endif + +static int pcf85263_init_hw(struct pcf85263 *pcf85263) +{ + struct device_node *np = pcf85263->dev->of_node; + unsigned int regval; + u32 propval; + int ret; + + /* Determine if oscilator has been stopped (probably low power) */ + ret = pcf85263_check_osc_stopped(pcf85263); + if (ret < 0) { + /* Log here since this is the first hw access on probe */ + dev_err(pcf85263->dev, "Unable to read register\n"); + + return ret; + } + + /* Determine 12/24H mode */ + ret = regmap_read(pcf85263->regmap, PCF85263_REG_OSC, ®val); + if (ret) + return ret; + pcf85263->mode_12h = !!(regval & PCF85263_REG_OSC_12H); + + /* Set oscilator register */ + regval &= ~PCF85263_REG_OSC_12H; /* keep current 12/24 h setting */ + + propval = PCF85263_QUARTZCAP_12p5pF; + of_property_read_u32(np, "quartz-load-capacitance", &propval); + regval |= ((propval << PCF85263_REG_OSC_CL_SHIFT) + & PCF85263_REG_OSC_CL_MASK); + + propval = PCF85263_QUARTZDRIVE_NORMAL; + of_property_read_u32(np, "quartz-drive-strength", &propval); + regval |= ((propval << PCF85263_REG_OSC_OSCD_SHIFT) + & PCF85263_REG_OSC_OSCD_MASK); + + if (of_property_read_bool(np, "quartz-low-jitter")) + regval |= PCF85263_REG_OSC_LOWJ; + + ret = regmap_write(pcf85263->regmap, PCF85263_REG_OSC, regval); + if (ret) + return ret; + + /* Set function register (RTC mode, 1s tick, clock output static) */ + ret = regmap_write(pcf85263->regmap, PCF85263_REG_FUNCTION, + PCF85263_REG_FUNCTION_COF_OFF); + if (ret) + return ret; + + /* Set all interrupts to disabled, level mode */ + ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTA_CTL, + PCF85263_REG_INTx_CTL_ILP); + if (ret) + return ret; + ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTB_CTL, + PCF85263_REG_INTx_CTL_ILP); + if (ret) + return ret; + + /* Setup IO pin config register */ + regval = PCF85263_REG_PINIO_CLKDISABLE; + switch (pcf85263->irq_pin) { + case PCF85263_IRQPIN_INTA: + regval |= (PCF85263_INTAPM_INTA | PCF85263_TSPM_DISABLED); + break; + case PCF85263_IRQPIN_INTB: + regval |= (PCF85263_INTAPM_HIGHZ | PCF85263_TSPM_INTB); + break; + case PCF85263_IRQPIN_NONE: + regval |= (PCF85263_INTAPM_HIGHZ | PCF85263_TSPM_DISABLED); + break; + } + ret = regmap_write(pcf85263->regmap, PCF85263_REG_PINIO, regval); + + return ret; +} + +static const struct rtc_class_ops rtc_ops = { + .ioctl = pcf85263_ioctl, + .read_time = pcf85263_read_time, + .set_time = pcf85263_set_time, + .read_alarm = pcf85263_read_alarm, + .set_alarm = pcf85263_set_alarm, + .alarm_irq_enable = pcf85263_alarm_irq_enable, +}; + +static const struct regmap_config pcf85263_regmap_cfg = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PCF85263_MAX_REG, +}; + +/* + * On some boards the interrupt line may not be wired to the CPU but only to + * a power supply circuit. + * In that case no interrupt will be specified in the device tree but the + * wakeup-source DT property may be used to enable wakeup programming in + * sysfs + */ +static bool pcf85263_can_wakeup_machine(struct pcf85263 *pcf85263) +{ + return pcf85263->irq || + of_property_read_bool(pcf85263->dev->of_node, "wakeup-source"); +} + +static int pcf85263_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct pcf85263 *pcf85263; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK)) + return -ENODEV; + + pcf85263 = devm_kzalloc(dev, sizeof(*pcf85263), GFP_KERNEL); + if (!pcf85263) + return -ENOMEM; + + pcf85263->dev = dev; + pcf85263->irq = client->irq; + dev_set_drvdata(dev, pcf85263); + + pcf85263->regmap = devm_regmap_init_i2c(client, &pcf85263_regmap_cfg); + if (IS_ERR(pcf85263->regmap)) { + ret = PTR_ERR(pcf85263->regmap); + dev_err(dev, "regmap allocation failed (%d)\n", ret); + + return ret; + } + + /* Determine which interrupt pin the board uses */ + if (pcf85263_can_wakeup_machine(pcf85263)) { + if (of_property_match_string(dev->of_node, + "interrupt-names", "INTB") >= 0) + pcf85263->irq_pin = PCF85263_IRQPIN_INTB; + else + pcf85263->irq_pin = PCF85263_IRQPIN_INTA; + } else { + pcf85263->irq_pin = PCF85263_IRQPIN_NONE; + } + + ret = pcf85263_init_hw(pcf85263); + if (ret) + return ret; + + if (pcf85263->irq) { + ret = devm_request_threaded_irq(dev, pcf85263->irq, NULL, + pcf85263_irq, + IRQF_ONESHOT, + dev->driver->name, pcf85263); + if (ret) { + dev_err(dev, "irq %d unavailable (%d)\n", + pcf85263->irq, ret); + pcf85263->irq = 0; + } + } + + if (pcf85263_can_wakeup_machine(pcf85263)) + device_init_wakeup(dev, true); + + pcf85263->rtc = devm_rtc_device_register(dev, dev->driver->name, + &rtc_ops, THIS_MODULE); + ret = PTR_ERR_OR_ZERO(pcf85263->rtc); + if (ret) + return ret; + + /* We cannot support UIE mode if we do not have an IRQ line */ + if (!pcf85263->irq) + pcf85263->rtc->uie_unsupported = 1; + + dev_info(pcf85263->dev, + "PCF85263 RTC (irqpin=%s irq=%d)\n", + pcf85263_irqpin_names[pcf85263->irq_pin], + pcf85263->irq); + + return 0; +} + +static int pcf85263_remove(struct i2c_client *client) +{ + struct pcf85263 *pcf85263 = i2c_get_clientdata(client); + + if (pcf85263_can_wakeup_machine(pcf85263)) + device_init_wakeup(pcf85263->dev, false); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pcf85263_suspend(struct device *dev) +{ + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); + int ret = 0; + + if (device_may_wakeup(dev)) + ret = enable_irq_wake(pcf85263->irq); + + return ret; +} + +static int pcf85263_resume(struct device *dev) +{ + struct pcf85263 *pcf85263 = dev_get_drvdata(dev); + int ret = 0; + + if (device_may_wakeup(dev)) + ret = disable_irq_wake(pcf85263->irq); + + return ret; +} + +#endif + +static const struct i2c_device_id pcf85263_id[] = { + { "pcf85263", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcf85263_id); + +#ifdef CONFIG_OF +static const struct of_device_id pcf85263_of_match[] = { + { .compatible = "nxp,pcf85263" }, + {} +}; +MODULE_DEVICE_TABLE(of, pcf85263_of_match); +#endif + +static SIMPLE_DEV_PM_OPS(pcf85263_pm_ops, pcf85263_suspend, pcf85263_resume); + +static struct i2c_driver pcf85263_driver = { + .driver = { + .name = "rtc-pcf85263", + .of_match_table = of_match_ptr(pcf85263_of_match), + .pm = &pcf85263_pm_ops, + }, + .probe = pcf85263_probe, + .remove = pcf85263_remove, + .id_table = pcf85263_id, +}; + +module_i2c_driver(pcf85263_driver); + +MODULE_AUTHOR("Martin Fuzzey <mfuzzey@parkeon.com>"); +MODULE_DESCRIPTION("PCF85263 RTC Driver"); +MODULE_LICENSE("GPL");
Add basic support for NXP PCF85263 I2C RTC chip. Only RTC and Alarm functions are supported. Signed-off-by: Martin Fuzzey <mfuzzey@parkeon.com> --- drivers/rtc/Kconfig | 8 + drivers/rtc/Makefile | 1 drivers/rtc/rtc-pcf85263.c | 655 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 664 insertions(+) create mode 100644 drivers/rtc/rtc-pcf85263.c