18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * A SPI driver for the Ricoh RS5C348 RTC
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * The board specific init code should provide characteristics of this
88c2ecf20Sopenharmony_ci * device:
98c2ecf20Sopenharmony_ci *     Mode 1 (High-Active, Shift-Then-Sample), High Avtive CS
108c2ecf20Sopenharmony_ci */
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <linux/bcd.h>
138c2ecf20Sopenharmony_ci#include <linux/delay.h>
148c2ecf20Sopenharmony_ci#include <linux/device.h>
158c2ecf20Sopenharmony_ci#include <linux/errno.h>
168c2ecf20Sopenharmony_ci#include <linux/init.h>
178c2ecf20Sopenharmony_ci#include <linux/kernel.h>
188c2ecf20Sopenharmony_ci#include <linux/string.h>
198c2ecf20Sopenharmony_ci#include <linux/slab.h>
208c2ecf20Sopenharmony_ci#include <linux/rtc.h>
218c2ecf20Sopenharmony_ci#include <linux/workqueue.h>
228c2ecf20Sopenharmony_ci#include <linux/spi/spi.h>
238c2ecf20Sopenharmony_ci#include <linux/module.h>
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#define RS5C348_REG_SECS	0
268c2ecf20Sopenharmony_ci#define RS5C348_REG_MINS	1
278c2ecf20Sopenharmony_ci#define RS5C348_REG_HOURS	2
288c2ecf20Sopenharmony_ci#define RS5C348_REG_WDAY	3
298c2ecf20Sopenharmony_ci#define RS5C348_REG_DAY	4
308c2ecf20Sopenharmony_ci#define RS5C348_REG_MONTH	5
318c2ecf20Sopenharmony_ci#define RS5C348_REG_YEAR	6
328c2ecf20Sopenharmony_ci#define RS5C348_REG_CTL1	14
338c2ecf20Sopenharmony_ci#define RS5C348_REG_CTL2	15
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci#define RS5C348_SECS_MASK	0x7f
368c2ecf20Sopenharmony_ci#define RS5C348_MINS_MASK	0x7f
378c2ecf20Sopenharmony_ci#define RS5C348_HOURS_MASK	0x3f
388c2ecf20Sopenharmony_ci#define RS5C348_WDAY_MASK	0x03
398c2ecf20Sopenharmony_ci#define RS5C348_DAY_MASK	0x3f
408c2ecf20Sopenharmony_ci#define RS5C348_MONTH_MASK	0x1f
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#define RS5C348_BIT_PM	0x20	/* REG_HOURS */
438c2ecf20Sopenharmony_ci#define RS5C348_BIT_Y2K	0x80	/* REG_MONTH */
448c2ecf20Sopenharmony_ci#define RS5C348_BIT_24H	0x20	/* REG_CTL1 */
458c2ecf20Sopenharmony_ci#define RS5C348_BIT_XSTP	0x10	/* REG_CTL2 */
468c2ecf20Sopenharmony_ci#define RS5C348_BIT_VDET	0x40	/* REG_CTL2 */
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci#define RS5C348_CMD_W(addr)	(((addr) << 4) | 0x08)	/* single write */
498c2ecf20Sopenharmony_ci#define RS5C348_CMD_R(addr)	(((addr) << 4) | 0x0c)	/* single read */
508c2ecf20Sopenharmony_ci#define RS5C348_CMD_MW(addr)	(((addr) << 4) | 0x00)	/* burst write */
518c2ecf20Sopenharmony_ci#define RS5C348_CMD_MR(addr)	(((addr) << 4) | 0x04)	/* burst read */
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistruct rs5c348_plat_data {
548c2ecf20Sopenharmony_ci	struct rtc_device *rtc;
558c2ecf20Sopenharmony_ci	int rtc_24h;
568c2ecf20Sopenharmony_ci};
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_cistatic int
598c2ecf20Sopenharmony_cirs5c348_rtc_set_time(struct device *dev, struct rtc_time *tm)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	struct spi_device *spi = to_spi_device(dev);
628c2ecf20Sopenharmony_ci	struct rs5c348_plat_data *pdata = dev_get_platdata(&spi->dev);
638c2ecf20Sopenharmony_ci	u8 txbuf[5+7], *txp;
648c2ecf20Sopenharmony_ci	int ret;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	ret = spi_w8r8(spi, RS5C348_CMD_R(RS5C348_REG_CTL2));
678c2ecf20Sopenharmony_ci	if (ret < 0)
688c2ecf20Sopenharmony_ci		return ret;
698c2ecf20Sopenharmony_ci	if (ret & RS5C348_BIT_XSTP) {
708c2ecf20Sopenharmony_ci		txbuf[0] = RS5C348_CMD_W(RS5C348_REG_CTL2);
718c2ecf20Sopenharmony_ci		txbuf[1] = 0;
728c2ecf20Sopenharmony_ci		ret = spi_write_then_read(spi, txbuf, 2, NULL, 0);
738c2ecf20Sopenharmony_ci		if (ret < 0)
748c2ecf20Sopenharmony_ci			return ret;
758c2ecf20Sopenharmony_ci	}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	/* Transfer 5 bytes before writing SEC.  This gives 31us for carry. */
788c2ecf20Sopenharmony_ci	txp = txbuf;
798c2ecf20Sopenharmony_ci	txbuf[0] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */
808c2ecf20Sopenharmony_ci	txbuf[1] = 0;	/* dummy */
818c2ecf20Sopenharmony_ci	txbuf[2] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */
828c2ecf20Sopenharmony_ci	txbuf[3] = 0;	/* dummy */
838c2ecf20Sopenharmony_ci	txbuf[4] = RS5C348_CMD_MW(RS5C348_REG_SECS); /* cmd, sec, ... */
848c2ecf20Sopenharmony_ci	txp = &txbuf[5];
858c2ecf20Sopenharmony_ci	txp[RS5C348_REG_SECS] = bin2bcd(tm->tm_sec);
868c2ecf20Sopenharmony_ci	txp[RS5C348_REG_MINS] = bin2bcd(tm->tm_min);
878c2ecf20Sopenharmony_ci	if (pdata->rtc_24h) {
888c2ecf20Sopenharmony_ci		txp[RS5C348_REG_HOURS] = bin2bcd(tm->tm_hour);
898c2ecf20Sopenharmony_ci	} else {
908c2ecf20Sopenharmony_ci		/* hour 0 is AM12, noon is PM12 */
918c2ecf20Sopenharmony_ci		txp[RS5C348_REG_HOURS] = bin2bcd((tm->tm_hour + 11) % 12 + 1) |
928c2ecf20Sopenharmony_ci			(tm->tm_hour >= 12 ? RS5C348_BIT_PM : 0);
938c2ecf20Sopenharmony_ci	}
948c2ecf20Sopenharmony_ci	txp[RS5C348_REG_WDAY] = bin2bcd(tm->tm_wday);
958c2ecf20Sopenharmony_ci	txp[RS5C348_REG_DAY] = bin2bcd(tm->tm_mday);
968c2ecf20Sopenharmony_ci	txp[RS5C348_REG_MONTH] = bin2bcd(tm->tm_mon + 1) |
978c2ecf20Sopenharmony_ci		(tm->tm_year >= 100 ? RS5C348_BIT_Y2K : 0);
988c2ecf20Sopenharmony_ci	txp[RS5C348_REG_YEAR] = bin2bcd(tm->tm_year % 100);
998c2ecf20Sopenharmony_ci	/* write in one transfer to avoid data inconsistency */
1008c2ecf20Sopenharmony_ci	ret = spi_write_then_read(spi, txbuf, sizeof(txbuf), NULL, 0);
1018c2ecf20Sopenharmony_ci	udelay(62);	/* Tcsr 62us */
1028c2ecf20Sopenharmony_ci	return ret;
1038c2ecf20Sopenharmony_ci}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_cistatic int
1068c2ecf20Sopenharmony_cirs5c348_rtc_read_time(struct device *dev, struct rtc_time *tm)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	struct spi_device *spi = to_spi_device(dev);
1098c2ecf20Sopenharmony_ci	struct rs5c348_plat_data *pdata = dev_get_platdata(&spi->dev);
1108c2ecf20Sopenharmony_ci	u8 txbuf[5], rxbuf[7];
1118c2ecf20Sopenharmony_ci	int ret;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	ret = spi_w8r8(spi, RS5C348_CMD_R(RS5C348_REG_CTL2));
1148c2ecf20Sopenharmony_ci	if (ret < 0)
1158c2ecf20Sopenharmony_ci		return ret;
1168c2ecf20Sopenharmony_ci	if (ret & RS5C348_BIT_VDET)
1178c2ecf20Sopenharmony_ci		dev_warn(&spi->dev, "voltage-low detected.\n");
1188c2ecf20Sopenharmony_ci	if (ret & RS5C348_BIT_XSTP) {
1198c2ecf20Sopenharmony_ci		dev_warn(&spi->dev, "oscillator-stop detected.\n");
1208c2ecf20Sopenharmony_ci		return -EINVAL;
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	/* Transfer 5 byte befores reading SEC.  This gives 31us for carry. */
1248c2ecf20Sopenharmony_ci	txbuf[0] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */
1258c2ecf20Sopenharmony_ci	txbuf[1] = 0;	/* dummy */
1268c2ecf20Sopenharmony_ci	txbuf[2] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */
1278c2ecf20Sopenharmony_ci	txbuf[3] = 0;	/* dummy */
1288c2ecf20Sopenharmony_ci	txbuf[4] = RS5C348_CMD_MR(RS5C348_REG_SECS); /* cmd, sec, ... */
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	/* read in one transfer to avoid data inconsistency */
1318c2ecf20Sopenharmony_ci	ret = spi_write_then_read(spi, txbuf, sizeof(txbuf),
1328c2ecf20Sopenharmony_ci				  rxbuf, sizeof(rxbuf));
1338c2ecf20Sopenharmony_ci	udelay(62);	/* Tcsr 62us */
1348c2ecf20Sopenharmony_ci	if (ret < 0)
1358c2ecf20Sopenharmony_ci		return ret;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	tm->tm_sec = bcd2bin(rxbuf[RS5C348_REG_SECS] & RS5C348_SECS_MASK);
1388c2ecf20Sopenharmony_ci	tm->tm_min = bcd2bin(rxbuf[RS5C348_REG_MINS] & RS5C348_MINS_MASK);
1398c2ecf20Sopenharmony_ci	tm->tm_hour = bcd2bin(rxbuf[RS5C348_REG_HOURS] & RS5C348_HOURS_MASK);
1408c2ecf20Sopenharmony_ci	if (!pdata->rtc_24h) {
1418c2ecf20Sopenharmony_ci		if (rxbuf[RS5C348_REG_HOURS] & RS5C348_BIT_PM) {
1428c2ecf20Sopenharmony_ci			tm->tm_hour -= 20;
1438c2ecf20Sopenharmony_ci			tm->tm_hour %= 12;
1448c2ecf20Sopenharmony_ci			tm->tm_hour += 12;
1458c2ecf20Sopenharmony_ci		} else
1468c2ecf20Sopenharmony_ci			tm->tm_hour %= 12;
1478c2ecf20Sopenharmony_ci	}
1488c2ecf20Sopenharmony_ci	tm->tm_wday = bcd2bin(rxbuf[RS5C348_REG_WDAY] & RS5C348_WDAY_MASK);
1498c2ecf20Sopenharmony_ci	tm->tm_mday = bcd2bin(rxbuf[RS5C348_REG_DAY] & RS5C348_DAY_MASK);
1508c2ecf20Sopenharmony_ci	tm->tm_mon =
1518c2ecf20Sopenharmony_ci		bcd2bin(rxbuf[RS5C348_REG_MONTH] & RS5C348_MONTH_MASK) - 1;
1528c2ecf20Sopenharmony_ci	/* year is 1900 + tm->tm_year */
1538c2ecf20Sopenharmony_ci	tm->tm_year = bcd2bin(rxbuf[RS5C348_REG_YEAR]) +
1548c2ecf20Sopenharmony_ci		((rxbuf[RS5C348_REG_MONTH] & RS5C348_BIT_Y2K) ? 100 : 0);
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	return 0;
1578c2ecf20Sopenharmony_ci}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_cistatic const struct rtc_class_ops rs5c348_rtc_ops = {
1608c2ecf20Sopenharmony_ci	.read_time	= rs5c348_rtc_read_time,
1618c2ecf20Sopenharmony_ci	.set_time	= rs5c348_rtc_set_time,
1628c2ecf20Sopenharmony_ci};
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_cistatic int rs5c348_probe(struct spi_device *spi)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	int ret;
1678c2ecf20Sopenharmony_ci	struct rtc_device *rtc;
1688c2ecf20Sopenharmony_ci	struct rs5c348_plat_data *pdata;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	pdata = devm_kzalloc(&spi->dev, sizeof(struct rs5c348_plat_data),
1718c2ecf20Sopenharmony_ci				GFP_KERNEL);
1728c2ecf20Sopenharmony_ci	if (!pdata)
1738c2ecf20Sopenharmony_ci		return -ENOMEM;
1748c2ecf20Sopenharmony_ci	spi->dev.platform_data = pdata;
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	/* Check D7 of SECOND register */
1778c2ecf20Sopenharmony_ci	ret = spi_w8r8(spi, RS5C348_CMD_R(RS5C348_REG_SECS));
1788c2ecf20Sopenharmony_ci	if (ret < 0 || (ret & 0x80)) {
1798c2ecf20Sopenharmony_ci		dev_err(&spi->dev, "not found.\n");
1808c2ecf20Sopenharmony_ci		return ret;
1818c2ecf20Sopenharmony_ci	}
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	dev_info(&spi->dev, "spiclk %u KHz.\n",
1848c2ecf20Sopenharmony_ci		 (spi->max_speed_hz + 500) / 1000);
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	ret = spi_w8r8(spi, RS5C348_CMD_R(RS5C348_REG_CTL1));
1878c2ecf20Sopenharmony_ci	if (ret < 0)
1888c2ecf20Sopenharmony_ci		return ret;
1898c2ecf20Sopenharmony_ci	if (ret & RS5C348_BIT_24H)
1908c2ecf20Sopenharmony_ci		pdata->rtc_24h = 1;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	rtc = devm_rtc_allocate_device(&spi->dev);
1938c2ecf20Sopenharmony_ci	if (IS_ERR(rtc))
1948c2ecf20Sopenharmony_ci		return PTR_ERR(rtc);
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	pdata->rtc = rtc;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	rtc->ops = &rs5c348_rtc_ops;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	return rtc_register_device(rtc);
2018c2ecf20Sopenharmony_ci}
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_cistatic struct spi_driver rs5c348_driver = {
2048c2ecf20Sopenharmony_ci	.driver = {
2058c2ecf20Sopenharmony_ci		.name	= "rtc-rs5c348",
2068c2ecf20Sopenharmony_ci	},
2078c2ecf20Sopenharmony_ci	.probe	= rs5c348_probe,
2088c2ecf20Sopenharmony_ci};
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_cimodule_spi_driver(rs5c348_driver);
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ciMODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
2138c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Ricoh RS5C348 RTC driver");
2148c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2158c2ecf20Sopenharmony_ciMODULE_ALIAS("spi:rtc-rs5c348");
216