18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * An SPI driver for the Philips PCF2123 RTC
48c2ecf20Sopenharmony_ci * Copyright 2009 Cyber Switching, Inc.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Author: Chris Verges <chrisv@cyberswitching.com>
78c2ecf20Sopenharmony_ci * Maintainers: http://www.cyberswitching.com
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * based on the RS5C348 driver in this same directory.
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci * Thanks to Christian Pellegrin <chripell@fsfe.org> for
128c2ecf20Sopenharmony_ci * the sysfs contributions to this driver.
138c2ecf20Sopenharmony_ci *
148c2ecf20Sopenharmony_ci * Please note that the CS is active high, so platform data
158c2ecf20Sopenharmony_ci * should look something like:
168c2ecf20Sopenharmony_ci *
178c2ecf20Sopenharmony_ci * static struct spi_board_info ek_spi_devices[] = {
188c2ecf20Sopenharmony_ci *	...
198c2ecf20Sopenharmony_ci *	{
208c2ecf20Sopenharmony_ci *		.modalias		= "rtc-pcf2123",
218c2ecf20Sopenharmony_ci *		.chip_select		= 1,
228c2ecf20Sopenharmony_ci *		.controller_data	= (void *)AT91_PIN_PA10,
238c2ecf20Sopenharmony_ci *		.max_speed_hz		= 1000 * 1000,
248c2ecf20Sopenharmony_ci *		.mode			= SPI_CS_HIGH,
258c2ecf20Sopenharmony_ci *		.bus_num		= 0,
268c2ecf20Sopenharmony_ci *	},
278c2ecf20Sopenharmony_ci *	...
288c2ecf20Sopenharmony_ci *};
298c2ecf20Sopenharmony_ci */
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#include <linux/bcd.h>
328c2ecf20Sopenharmony_ci#include <linux/delay.h>
338c2ecf20Sopenharmony_ci#include <linux/device.h>
348c2ecf20Sopenharmony_ci#include <linux/errno.h>
358c2ecf20Sopenharmony_ci#include <linux/init.h>
368c2ecf20Sopenharmony_ci#include <linux/kernel.h>
378c2ecf20Sopenharmony_ci#include <linux/of.h>
388c2ecf20Sopenharmony_ci#include <linux/string.h>
398c2ecf20Sopenharmony_ci#include <linux/slab.h>
408c2ecf20Sopenharmony_ci#include <linux/rtc.h>
418c2ecf20Sopenharmony_ci#include <linux/spi/spi.h>
428c2ecf20Sopenharmony_ci#include <linux/module.h>
438c2ecf20Sopenharmony_ci#include <linux/regmap.h>
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/* REGISTERS */
468c2ecf20Sopenharmony_ci#define PCF2123_REG_CTRL1	(0x00)	/* Control Register 1 */
478c2ecf20Sopenharmony_ci#define PCF2123_REG_CTRL2	(0x01)	/* Control Register 2 */
488c2ecf20Sopenharmony_ci#define PCF2123_REG_SC		(0x02)	/* datetime */
498c2ecf20Sopenharmony_ci#define PCF2123_REG_MN		(0x03)
508c2ecf20Sopenharmony_ci#define PCF2123_REG_HR		(0x04)
518c2ecf20Sopenharmony_ci#define PCF2123_REG_DM		(0x05)
528c2ecf20Sopenharmony_ci#define PCF2123_REG_DW		(0x06)
538c2ecf20Sopenharmony_ci#define PCF2123_REG_MO		(0x07)
548c2ecf20Sopenharmony_ci#define PCF2123_REG_YR		(0x08)
558c2ecf20Sopenharmony_ci#define PCF2123_REG_ALRM_MN	(0x09)	/* Alarm Registers */
568c2ecf20Sopenharmony_ci#define PCF2123_REG_ALRM_HR	(0x0a)
578c2ecf20Sopenharmony_ci#define PCF2123_REG_ALRM_DM	(0x0b)
588c2ecf20Sopenharmony_ci#define PCF2123_REG_ALRM_DW	(0x0c)
598c2ecf20Sopenharmony_ci#define PCF2123_REG_OFFSET	(0x0d)	/* Clock Rate Offset Register */
608c2ecf20Sopenharmony_ci#define PCF2123_REG_TMR_CLKOUT	(0x0e)	/* Timer Registers */
618c2ecf20Sopenharmony_ci#define PCF2123_REG_CTDWN_TMR	(0x0f)
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci/* PCF2123_REG_CTRL1 BITS */
648c2ecf20Sopenharmony_ci#define CTRL1_CLEAR		(0)	/* Clear */
658c2ecf20Sopenharmony_ci#define CTRL1_CORR_INT		BIT(1)	/* Correction irq enable */
668c2ecf20Sopenharmony_ci#define CTRL1_12_HOUR		BIT(2)	/* 12 hour time */
678c2ecf20Sopenharmony_ci#define CTRL1_SW_RESET	(BIT(3) | BIT(4) | BIT(6))	/* Software reset */
688c2ecf20Sopenharmony_ci#define CTRL1_STOP		BIT(5)	/* Stop the clock */
698c2ecf20Sopenharmony_ci#define CTRL1_EXT_TEST		BIT(7)	/* External clock test mode */
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci/* PCF2123_REG_CTRL2 BITS */
728c2ecf20Sopenharmony_ci#define CTRL2_TIE		BIT(0)	/* Countdown timer irq enable */
738c2ecf20Sopenharmony_ci#define CTRL2_AIE		BIT(1)	/* Alarm irq enable */
748c2ecf20Sopenharmony_ci#define CTRL2_TF		BIT(2)	/* Countdown timer flag */
758c2ecf20Sopenharmony_ci#define CTRL2_AF		BIT(3)	/* Alarm flag */
768c2ecf20Sopenharmony_ci#define CTRL2_TI_TP		BIT(4)	/* Irq pin generates pulse */
778c2ecf20Sopenharmony_ci#define CTRL2_MSF		BIT(5)	/* Minute or second irq flag */
788c2ecf20Sopenharmony_ci#define CTRL2_SI		BIT(6)	/* Second irq enable */
798c2ecf20Sopenharmony_ci#define CTRL2_MI		BIT(7)	/* Minute irq enable */
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci/* PCF2123_REG_SC BITS */
828c2ecf20Sopenharmony_ci#define OSC_HAS_STOPPED		BIT(7)	/* Clock has been stopped */
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci/* PCF2123_REG_ALRM_XX BITS */
858c2ecf20Sopenharmony_ci#define ALRM_DISABLE		BIT(7)	/* MN, HR, DM, or DW alarm matching */
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci/* PCF2123_REG_TMR_CLKOUT BITS */
888c2ecf20Sopenharmony_ci#define CD_TMR_4096KHZ		(0)	/* 4096 KHz countdown timer */
898c2ecf20Sopenharmony_ci#define CD_TMR_64HZ		(1)	/* 64 Hz countdown timer */
908c2ecf20Sopenharmony_ci#define CD_TMR_1HZ		(2)	/* 1 Hz countdown timer */
918c2ecf20Sopenharmony_ci#define CD_TMR_60th_HZ		(3)	/* 60th Hz countdown timer */
928c2ecf20Sopenharmony_ci#define CD_TMR_TE		BIT(3)	/* Countdown timer enable */
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci/* PCF2123_REG_OFFSET BITS */
958c2ecf20Sopenharmony_ci#define OFFSET_SIGN_BIT		6	/* 2's complement sign bit */
968c2ecf20Sopenharmony_ci#define OFFSET_COARSE		BIT(7)	/* Coarse mode offset */
978c2ecf20Sopenharmony_ci#define OFFSET_STEP		(2170)	/* Offset step in parts per billion */
988c2ecf20Sopenharmony_ci#define OFFSET_MASK		GENMASK(6, 0)	/* Offset value */
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci/* READ/WRITE ADDRESS BITS */
1018c2ecf20Sopenharmony_ci#define PCF2123_WRITE		BIT(4)
1028c2ecf20Sopenharmony_ci#define PCF2123_READ		(BIT(4) | BIT(7))
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_cistatic struct spi_driver pcf2123_driver;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_cistruct pcf2123_data {
1088c2ecf20Sopenharmony_ci	struct rtc_device *rtc;
1098c2ecf20Sopenharmony_ci	struct regmap *map;
1108c2ecf20Sopenharmony_ci};
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic const struct regmap_config pcf2123_regmap_config = {
1138c2ecf20Sopenharmony_ci	.reg_bits = 8,
1148c2ecf20Sopenharmony_ci	.val_bits = 8,
1158c2ecf20Sopenharmony_ci	.read_flag_mask = PCF2123_READ,
1168c2ecf20Sopenharmony_ci	.write_flag_mask = PCF2123_WRITE,
1178c2ecf20Sopenharmony_ci	.max_register = PCF2123_REG_CTDWN_TMR,
1188c2ecf20Sopenharmony_ci};
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_cistatic int pcf2123_read_offset(struct device *dev, long *offset)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	struct pcf2123_data *pcf2123 = dev_get_drvdata(dev);
1238c2ecf20Sopenharmony_ci	int ret, val;
1248c2ecf20Sopenharmony_ci	unsigned int reg;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	ret = regmap_read(pcf2123->map, PCF2123_REG_OFFSET, &reg);
1278c2ecf20Sopenharmony_ci	if (ret)
1288c2ecf20Sopenharmony_ci		return ret;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	val = sign_extend32((reg & OFFSET_MASK), OFFSET_SIGN_BIT);
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	if (reg & OFFSET_COARSE)
1338c2ecf20Sopenharmony_ci		val *= 2;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	*offset = ((long)val) * OFFSET_STEP;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	return 0;
1388c2ecf20Sopenharmony_ci}
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci/*
1418c2ecf20Sopenharmony_ci * The offset register is a 7 bit signed value with a coarse bit in bit 7.
1428c2ecf20Sopenharmony_ci * The main difference between the two is normal offset adjusts the first
1438c2ecf20Sopenharmony_ci * second of n minutes every other hour, with 61, 62 and 63 being shoved
1448c2ecf20Sopenharmony_ci * into the 60th minute.
1458c2ecf20Sopenharmony_ci * The coarse adjustment does the same, but every hour.
1468c2ecf20Sopenharmony_ci * the two overlap, with every even normal offset value corresponding
1478c2ecf20Sopenharmony_ci * to a coarse offset. Based on this algorithm, it seems that despite the
1488c2ecf20Sopenharmony_ci * name, coarse offset is a better fit for overlapping values.
1498c2ecf20Sopenharmony_ci */
1508c2ecf20Sopenharmony_cistatic int pcf2123_set_offset(struct device *dev, long offset)
1518c2ecf20Sopenharmony_ci{
1528c2ecf20Sopenharmony_ci	struct pcf2123_data *pcf2123 = dev_get_drvdata(dev);
1538c2ecf20Sopenharmony_ci	s8 reg;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	if (offset > OFFSET_STEP * 127)
1568c2ecf20Sopenharmony_ci		reg = 127;
1578c2ecf20Sopenharmony_ci	else if (offset < OFFSET_STEP * -128)
1588c2ecf20Sopenharmony_ci		reg = -128;
1598c2ecf20Sopenharmony_ci	else
1608c2ecf20Sopenharmony_ci		reg = DIV_ROUND_CLOSEST(offset, OFFSET_STEP);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	/* choose fine offset only for odd values in the normal range */
1638c2ecf20Sopenharmony_ci	if (reg & 1 && reg <= 63 && reg >= -64) {
1648c2ecf20Sopenharmony_ci		/* Normal offset. Clear the coarse bit */
1658c2ecf20Sopenharmony_ci		reg &= ~OFFSET_COARSE;
1668c2ecf20Sopenharmony_ci	} else {
1678c2ecf20Sopenharmony_ci		/* Coarse offset. Divide by 2 and set the coarse bit */
1688c2ecf20Sopenharmony_ci		reg >>= 1;
1698c2ecf20Sopenharmony_ci		reg |= OFFSET_COARSE;
1708c2ecf20Sopenharmony_ci	}
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	return regmap_write(pcf2123->map, PCF2123_REG_OFFSET, (unsigned int)reg);
1738c2ecf20Sopenharmony_ci}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_cistatic int pcf2123_rtc_read_time(struct device *dev, struct rtc_time *tm)
1768c2ecf20Sopenharmony_ci{
1778c2ecf20Sopenharmony_ci	struct pcf2123_data *pcf2123 = dev_get_drvdata(dev);
1788c2ecf20Sopenharmony_ci	u8 rxbuf[7];
1798c2ecf20Sopenharmony_ci	int ret;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	ret = regmap_bulk_read(pcf2123->map, PCF2123_REG_SC, rxbuf,
1828c2ecf20Sopenharmony_ci				sizeof(rxbuf));
1838c2ecf20Sopenharmony_ci	if (ret)
1848c2ecf20Sopenharmony_ci		return ret;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	if (rxbuf[0] & OSC_HAS_STOPPED) {
1878c2ecf20Sopenharmony_ci		dev_info(dev, "clock was stopped. Time is not valid\n");
1888c2ecf20Sopenharmony_ci		return -EINVAL;
1898c2ecf20Sopenharmony_ci	}
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	tm->tm_sec = bcd2bin(rxbuf[0] & 0x7F);
1928c2ecf20Sopenharmony_ci	tm->tm_min = bcd2bin(rxbuf[1] & 0x7F);
1938c2ecf20Sopenharmony_ci	tm->tm_hour = bcd2bin(rxbuf[2] & 0x3F); /* rtc hr 0-23 */
1948c2ecf20Sopenharmony_ci	tm->tm_mday = bcd2bin(rxbuf[3] & 0x3F);
1958c2ecf20Sopenharmony_ci	tm->tm_wday = rxbuf[4] & 0x07;
1968c2ecf20Sopenharmony_ci	tm->tm_mon = bcd2bin(rxbuf[5] & 0x1F) - 1; /* rtc mn 1-12 */
1978c2ecf20Sopenharmony_ci	tm->tm_year = bcd2bin(rxbuf[6]) + 100;
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s: tm is %ptR\n", __func__, tm);
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	return 0;
2028c2ecf20Sopenharmony_ci}
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_cistatic int pcf2123_rtc_set_time(struct device *dev, struct rtc_time *tm)
2058c2ecf20Sopenharmony_ci{
2068c2ecf20Sopenharmony_ci	struct pcf2123_data *pcf2123 = dev_get_drvdata(dev);
2078c2ecf20Sopenharmony_ci	u8 txbuf[7];
2088c2ecf20Sopenharmony_ci	int ret;
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s: tm is %ptR\n", __func__, tm);
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	/* Stop the counter first */
2138c2ecf20Sopenharmony_ci	ret = regmap_write(pcf2123->map, PCF2123_REG_CTRL1, CTRL1_STOP);
2148c2ecf20Sopenharmony_ci	if (ret)
2158c2ecf20Sopenharmony_ci		return ret;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	/* Set the new time */
2188c2ecf20Sopenharmony_ci	txbuf[0] = bin2bcd(tm->tm_sec & 0x7F);
2198c2ecf20Sopenharmony_ci	txbuf[1] = bin2bcd(tm->tm_min & 0x7F);
2208c2ecf20Sopenharmony_ci	txbuf[2] = bin2bcd(tm->tm_hour & 0x3F);
2218c2ecf20Sopenharmony_ci	txbuf[3] = bin2bcd(tm->tm_mday & 0x3F);
2228c2ecf20Sopenharmony_ci	txbuf[4] = tm->tm_wday & 0x07;
2238c2ecf20Sopenharmony_ci	txbuf[5] = bin2bcd((tm->tm_mon + 1) & 0x1F); /* rtc mn 1-12 */
2248c2ecf20Sopenharmony_ci	txbuf[6] = bin2bcd(tm->tm_year - 100);
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	ret = regmap_bulk_write(pcf2123->map, PCF2123_REG_SC, txbuf,
2278c2ecf20Sopenharmony_ci				sizeof(txbuf));
2288c2ecf20Sopenharmony_ci	if (ret)
2298c2ecf20Sopenharmony_ci		return ret;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	/* Start the counter */
2328c2ecf20Sopenharmony_ci	ret = regmap_write(pcf2123->map, PCF2123_REG_CTRL1, CTRL1_CLEAR);
2338c2ecf20Sopenharmony_ci	if (ret)
2348c2ecf20Sopenharmony_ci		return ret;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	return 0;
2378c2ecf20Sopenharmony_ci}
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_cistatic int pcf2123_rtc_alarm_irq_enable(struct device *dev, unsigned int en)
2408c2ecf20Sopenharmony_ci{
2418c2ecf20Sopenharmony_ci	struct pcf2123_data *pcf2123 = dev_get_drvdata(dev);
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	return regmap_update_bits(pcf2123->map, PCF2123_REG_CTRL2, CTRL2_AIE,
2448c2ecf20Sopenharmony_ci				  en ? CTRL2_AIE : 0);
2458c2ecf20Sopenharmony_ci}
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_cistatic int pcf2123_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
2488c2ecf20Sopenharmony_ci{
2498c2ecf20Sopenharmony_ci	struct pcf2123_data *pcf2123 = dev_get_drvdata(dev);
2508c2ecf20Sopenharmony_ci	u8 rxbuf[4];
2518c2ecf20Sopenharmony_ci	int ret;
2528c2ecf20Sopenharmony_ci	unsigned int val = 0;
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci	ret = regmap_bulk_read(pcf2123->map, PCF2123_REG_ALRM_MN, rxbuf,
2558c2ecf20Sopenharmony_ci				sizeof(rxbuf));
2568c2ecf20Sopenharmony_ci	if (ret)
2578c2ecf20Sopenharmony_ci		return ret;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	alm->time.tm_min = bcd2bin(rxbuf[0] & 0x7F);
2608c2ecf20Sopenharmony_ci	alm->time.tm_hour = bcd2bin(rxbuf[1] & 0x3F);
2618c2ecf20Sopenharmony_ci	alm->time.tm_mday = bcd2bin(rxbuf[2] & 0x3F);
2628c2ecf20Sopenharmony_ci	alm->time.tm_wday = bcd2bin(rxbuf[3] & 0x07);
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s: alm is %ptR\n", __func__, &alm->time);
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	ret = regmap_read(pcf2123->map, PCF2123_REG_CTRL2, &val);
2678c2ecf20Sopenharmony_ci	if (ret)
2688c2ecf20Sopenharmony_ci		return ret;
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	alm->enabled = !!(val & CTRL2_AIE);
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	return 0;
2738c2ecf20Sopenharmony_ci}
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_cistatic int pcf2123_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
2768c2ecf20Sopenharmony_ci{
2778c2ecf20Sopenharmony_ci	struct pcf2123_data *pcf2123 = dev_get_drvdata(dev);
2788c2ecf20Sopenharmony_ci	u8 txbuf[4];
2798c2ecf20Sopenharmony_ci	int ret;
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s: alm is %ptR\n", __func__, &alm->time);
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	/* Disable alarm interrupt */
2848c2ecf20Sopenharmony_ci	ret = regmap_update_bits(pcf2123->map, PCF2123_REG_CTRL2, CTRL2_AIE, 0);
2858c2ecf20Sopenharmony_ci	if (ret)
2868c2ecf20Sopenharmony_ci		return ret;
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	/* Ensure alarm flag is clear */
2898c2ecf20Sopenharmony_ci	ret = regmap_update_bits(pcf2123->map, PCF2123_REG_CTRL2, CTRL2_AF, 0);
2908c2ecf20Sopenharmony_ci	if (ret)
2918c2ecf20Sopenharmony_ci		return ret;
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci	/* Set new alarm */
2948c2ecf20Sopenharmony_ci	txbuf[0] = bin2bcd(alm->time.tm_min & 0x7F);
2958c2ecf20Sopenharmony_ci	txbuf[1] = bin2bcd(alm->time.tm_hour & 0x3F);
2968c2ecf20Sopenharmony_ci	txbuf[2] = bin2bcd(alm->time.tm_mday & 0x3F);
2978c2ecf20Sopenharmony_ci	txbuf[3] = ALRM_DISABLE;
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ci	ret = regmap_bulk_write(pcf2123->map, PCF2123_REG_ALRM_MN, txbuf,
3008c2ecf20Sopenharmony_ci				sizeof(txbuf));
3018c2ecf20Sopenharmony_ci	if (ret)
3028c2ecf20Sopenharmony_ci		return ret;
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci	return pcf2123_rtc_alarm_irq_enable(dev, alm->enabled);
3058c2ecf20Sopenharmony_ci}
3068c2ecf20Sopenharmony_ci
3078c2ecf20Sopenharmony_cistatic irqreturn_t pcf2123_rtc_irq(int irq, void *dev)
3088c2ecf20Sopenharmony_ci{
3098c2ecf20Sopenharmony_ci	struct pcf2123_data *pcf2123 = dev_get_drvdata(dev);
3108c2ecf20Sopenharmony_ci	struct mutex *lock = &pcf2123->rtc->ops_lock;
3118c2ecf20Sopenharmony_ci	unsigned int val = 0;
3128c2ecf20Sopenharmony_ci	int ret = IRQ_NONE;
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_ci	mutex_lock(lock);
3158c2ecf20Sopenharmony_ci	regmap_read(pcf2123->map, PCF2123_REG_CTRL2, &val);
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	/* Alarm? */
3188c2ecf20Sopenharmony_ci	if (val & CTRL2_AF) {
3198c2ecf20Sopenharmony_ci		ret = IRQ_HANDLED;
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci		/* Clear alarm flag */
3228c2ecf20Sopenharmony_ci		regmap_update_bits(pcf2123->map, PCF2123_REG_CTRL2, CTRL2_AF, 0);
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_ci		rtc_update_irq(pcf2123->rtc, 1, RTC_IRQF | RTC_AF);
3258c2ecf20Sopenharmony_ci	}
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci	mutex_unlock(lock);
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	return ret;
3308c2ecf20Sopenharmony_ci}
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_cistatic int pcf2123_reset(struct device *dev)
3338c2ecf20Sopenharmony_ci{
3348c2ecf20Sopenharmony_ci	struct pcf2123_data *pcf2123 = dev_get_drvdata(dev);
3358c2ecf20Sopenharmony_ci	int ret;
3368c2ecf20Sopenharmony_ci	unsigned int val = 0;
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci	ret = regmap_write(pcf2123->map, PCF2123_REG_CTRL1, CTRL1_SW_RESET);
3398c2ecf20Sopenharmony_ci	if (ret)
3408c2ecf20Sopenharmony_ci		return ret;
3418c2ecf20Sopenharmony_ci
3428c2ecf20Sopenharmony_ci	/* Stop the counter */
3438c2ecf20Sopenharmony_ci	dev_dbg(dev, "stopping RTC\n");
3448c2ecf20Sopenharmony_ci	ret = regmap_write(pcf2123->map, PCF2123_REG_CTRL1, CTRL1_STOP);
3458c2ecf20Sopenharmony_ci	if (ret)
3468c2ecf20Sopenharmony_ci		return ret;
3478c2ecf20Sopenharmony_ci
3488c2ecf20Sopenharmony_ci	/* See if the counter was actually stopped */
3498c2ecf20Sopenharmony_ci	dev_dbg(dev, "checking for presence of RTC\n");
3508c2ecf20Sopenharmony_ci	ret = regmap_read(pcf2123->map, PCF2123_REG_CTRL1, &val);
3518c2ecf20Sopenharmony_ci	if (ret)
3528c2ecf20Sopenharmony_ci		return ret;
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	dev_dbg(dev, "received data from RTC (0x%08X)\n", val);
3558c2ecf20Sopenharmony_ci	if (!(val & CTRL1_STOP))
3568c2ecf20Sopenharmony_ci		return -ENODEV;
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci	/* Start the counter */
3598c2ecf20Sopenharmony_ci	ret = regmap_write(pcf2123->map, PCF2123_REG_CTRL1, CTRL1_CLEAR);
3608c2ecf20Sopenharmony_ci	if (ret)
3618c2ecf20Sopenharmony_ci		return ret;
3628c2ecf20Sopenharmony_ci
3638c2ecf20Sopenharmony_ci	return 0;
3648c2ecf20Sopenharmony_ci}
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_cistatic const struct rtc_class_ops pcf2123_rtc_ops = {
3678c2ecf20Sopenharmony_ci	.read_time	= pcf2123_rtc_read_time,
3688c2ecf20Sopenharmony_ci	.set_time	= pcf2123_rtc_set_time,
3698c2ecf20Sopenharmony_ci	.read_offset	= pcf2123_read_offset,
3708c2ecf20Sopenharmony_ci	.set_offset	= pcf2123_set_offset,
3718c2ecf20Sopenharmony_ci	.read_alarm	= pcf2123_rtc_read_alarm,
3728c2ecf20Sopenharmony_ci	.set_alarm	= pcf2123_rtc_set_alarm,
3738c2ecf20Sopenharmony_ci	.alarm_irq_enable = pcf2123_rtc_alarm_irq_enable,
3748c2ecf20Sopenharmony_ci};
3758c2ecf20Sopenharmony_ci
3768c2ecf20Sopenharmony_cistatic int pcf2123_probe(struct spi_device *spi)
3778c2ecf20Sopenharmony_ci{
3788c2ecf20Sopenharmony_ci	struct rtc_device *rtc;
3798c2ecf20Sopenharmony_ci	struct rtc_time tm;
3808c2ecf20Sopenharmony_ci	struct pcf2123_data *pcf2123;
3818c2ecf20Sopenharmony_ci	int ret = 0;
3828c2ecf20Sopenharmony_ci
3838c2ecf20Sopenharmony_ci	pcf2123 = devm_kzalloc(&spi->dev, sizeof(struct pcf2123_data),
3848c2ecf20Sopenharmony_ci				GFP_KERNEL);
3858c2ecf20Sopenharmony_ci	if (!pcf2123)
3868c2ecf20Sopenharmony_ci		return -ENOMEM;
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_ci	dev_set_drvdata(&spi->dev, pcf2123);
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_ci	pcf2123->map = devm_regmap_init_spi(spi, &pcf2123_regmap_config);
3918c2ecf20Sopenharmony_ci	if (IS_ERR(pcf2123->map)) {
3928c2ecf20Sopenharmony_ci		dev_err(&spi->dev, "regmap init failed.\n");
3938c2ecf20Sopenharmony_ci		return PTR_ERR(pcf2123->map);
3948c2ecf20Sopenharmony_ci	}
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci	ret = pcf2123_rtc_read_time(&spi->dev, &tm);
3978c2ecf20Sopenharmony_ci	if (ret < 0) {
3988c2ecf20Sopenharmony_ci		ret = pcf2123_reset(&spi->dev);
3998c2ecf20Sopenharmony_ci		if (ret < 0) {
4008c2ecf20Sopenharmony_ci			dev_err(&spi->dev, "chip not found\n");
4018c2ecf20Sopenharmony_ci			return ret;
4028c2ecf20Sopenharmony_ci		}
4038c2ecf20Sopenharmony_ci	}
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_ci	dev_info(&spi->dev, "spiclk %u KHz.\n",
4068c2ecf20Sopenharmony_ci			(spi->max_speed_hz + 500) / 1000);
4078c2ecf20Sopenharmony_ci
4088c2ecf20Sopenharmony_ci	/* Finalize the initialization */
4098c2ecf20Sopenharmony_ci	rtc = devm_rtc_allocate_device(&spi->dev);
4108c2ecf20Sopenharmony_ci	if (IS_ERR(rtc))
4118c2ecf20Sopenharmony_ci		return PTR_ERR(rtc);
4128c2ecf20Sopenharmony_ci
4138c2ecf20Sopenharmony_ci	pcf2123->rtc = rtc;
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_ci	/* Register alarm irq */
4168c2ecf20Sopenharmony_ci	if (spi->irq > 0) {
4178c2ecf20Sopenharmony_ci		ret = devm_request_threaded_irq(&spi->dev, spi->irq, NULL,
4188c2ecf20Sopenharmony_ci				pcf2123_rtc_irq,
4198c2ecf20Sopenharmony_ci				IRQF_TRIGGER_LOW | IRQF_ONESHOT,
4208c2ecf20Sopenharmony_ci				pcf2123_driver.driver.name, &spi->dev);
4218c2ecf20Sopenharmony_ci		if (!ret)
4228c2ecf20Sopenharmony_ci			device_init_wakeup(&spi->dev, true);
4238c2ecf20Sopenharmony_ci		else
4248c2ecf20Sopenharmony_ci			dev_err(&spi->dev, "could not request irq.\n");
4258c2ecf20Sopenharmony_ci	}
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_ci	/* The PCF2123's alarm only has minute accuracy. Must add timer
4288c2ecf20Sopenharmony_ci	 * support to this driver to generate interrupts more than once
4298c2ecf20Sopenharmony_ci	 * per minute.
4308c2ecf20Sopenharmony_ci	 */
4318c2ecf20Sopenharmony_ci	rtc->uie_unsupported = 1;
4328c2ecf20Sopenharmony_ci	rtc->ops = &pcf2123_rtc_ops;
4338c2ecf20Sopenharmony_ci	rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
4348c2ecf20Sopenharmony_ci	rtc->range_max = RTC_TIMESTAMP_END_2099;
4358c2ecf20Sopenharmony_ci	rtc->set_start_time = true;
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_ci	ret = rtc_register_device(rtc);
4388c2ecf20Sopenharmony_ci	if (ret)
4398c2ecf20Sopenharmony_ci		return ret;
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci	return 0;
4428c2ecf20Sopenharmony_ci}
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_ci#ifdef CONFIG_OF
4458c2ecf20Sopenharmony_cistatic const struct of_device_id pcf2123_dt_ids[] = {
4468c2ecf20Sopenharmony_ci	{ .compatible = "nxp,pcf2123", },
4478c2ecf20Sopenharmony_ci	{ .compatible = "microcrystal,rv2123", },
4488c2ecf20Sopenharmony_ci	/* Deprecated, do not use */
4498c2ecf20Sopenharmony_ci	{ .compatible = "nxp,rtc-pcf2123", },
4508c2ecf20Sopenharmony_ci	{ /* sentinel */ }
4518c2ecf20Sopenharmony_ci};
4528c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, pcf2123_dt_ids);
4538c2ecf20Sopenharmony_ci#endif
4548c2ecf20Sopenharmony_ci
4558c2ecf20Sopenharmony_cistatic struct spi_driver pcf2123_driver = {
4568c2ecf20Sopenharmony_ci	.driver	= {
4578c2ecf20Sopenharmony_ci			.name	= "rtc-pcf2123",
4588c2ecf20Sopenharmony_ci			.of_match_table = of_match_ptr(pcf2123_dt_ids),
4598c2ecf20Sopenharmony_ci	},
4608c2ecf20Sopenharmony_ci	.probe	= pcf2123_probe,
4618c2ecf20Sopenharmony_ci};
4628c2ecf20Sopenharmony_ci
4638c2ecf20Sopenharmony_cimodule_spi_driver(pcf2123_driver);
4648c2ecf20Sopenharmony_ci
4658c2ecf20Sopenharmony_ciMODULE_AUTHOR("Chris Verges <chrisv@cyberswitching.com>");
4668c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("NXP PCF2123 RTC driver");
4678c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
468