xref: /openbmc/linux/drivers/rtc/rtc-m48t86.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
21d98af87SAlessandro Zummo /*
31d98af87SAlessandro Zummo  * ST M48T86 / Dallas DS12887 RTC driver
41d98af87SAlessandro Zummo  * Copyright (c) 2006 Tower Technologies
51d98af87SAlessandro Zummo  *
61d98af87SAlessandro Zummo  * Author: Alessandro Zummo <a.zummo@towertech.it>
71d98af87SAlessandro Zummo  *
81d98af87SAlessandro Zummo  * This drivers only supports the clock running in BCD and 24H mode.
91d98af87SAlessandro Zummo  * If it will be ever adapted to binary and 12H mode, care must be taken
101d98af87SAlessandro Zummo  * to not introduce bugs.
111d98af87SAlessandro Zummo  */
121d98af87SAlessandro Zummo 
131d98af87SAlessandro Zummo #include <linux/module.h>
14*6ec3f5ecSNikita Shubin #include <linux/mod_devicetable.h>
151d98af87SAlessandro Zummo #include <linux/rtc.h>
161d98af87SAlessandro Zummo #include <linux/platform_device.h>
171d98af87SAlessandro Zummo #include <linux/bcd.h>
188057c86dSH Hartley Sweeten #include <linux/io.h>
191d98af87SAlessandro Zummo 
2068b54f47SH Hartley Sweeten #define M48T86_SEC		0x00
2168b54f47SH Hartley Sweeten #define M48T86_SECALRM		0x01
2268b54f47SH Hartley Sweeten #define M48T86_MIN		0x02
2368b54f47SH Hartley Sweeten #define M48T86_MINALRM		0x03
2468b54f47SH Hartley Sweeten #define M48T86_HOUR		0x04
2568b54f47SH Hartley Sweeten #define M48T86_HOURALRM		0x05
2668b54f47SH Hartley Sweeten #define M48T86_DOW		0x06 /* 1 = sunday */
2768b54f47SH Hartley Sweeten #define M48T86_DOM		0x07
2868b54f47SH Hartley Sweeten #define M48T86_MONTH		0x08 /* 1 - 12 */
2968b54f47SH Hartley Sweeten #define M48T86_YEAR		0x09 /* 0 - 99 */
3068b54f47SH Hartley Sweeten #define M48T86_A		0x0a
3168b54f47SH Hartley Sweeten #define M48T86_B		0x0b
3268b54f47SH Hartley Sweeten #define M48T86_B_SET		BIT(7)
3368b54f47SH Hartley Sweeten #define M48T86_B_DM		BIT(2)
3468b54f47SH Hartley Sweeten #define M48T86_B_H24		BIT(1)
3568b54f47SH Hartley Sweeten #define M48T86_C		0x0c
3668b54f47SH Hartley Sweeten #define M48T86_D		0x0d
3768b54f47SH Hartley Sweeten #define M48T86_D_VRT		BIT(7)
38b180cf8bSH Hartley Sweeten #define M48T86_NVRAM(x)		(0x0e + (x))
39b180cf8bSH Hartley Sweeten #define M48T86_NVRAM_LEN	114
401d98af87SAlessandro Zummo 
418057c86dSH Hartley Sweeten struct m48t86_rtc_info {
428057c86dSH Hartley Sweeten 	void __iomem *index_reg;
438057c86dSH Hartley Sweeten 	void __iomem *data_reg;
448057c86dSH Hartley Sweeten 	struct rtc_device *rtc;
458057c86dSH Hartley Sweeten };
468057c86dSH Hartley Sweeten 
m48t86_readb(struct device * dev,unsigned long addr)478057c86dSH Hartley Sweeten static unsigned char m48t86_readb(struct device *dev, unsigned long addr)
488057c86dSH Hartley Sweeten {
498057c86dSH Hartley Sweeten 	struct m48t86_rtc_info *info = dev_get_drvdata(dev);
508057c86dSH Hartley Sweeten 	unsigned char value;
518057c86dSH Hartley Sweeten 
528057c86dSH Hartley Sweeten 	writeb(addr, info->index_reg);
538057c86dSH Hartley Sweeten 	value = readb(info->data_reg);
540500ce58SH Hartley Sweeten 
558057c86dSH Hartley Sweeten 	return value;
568057c86dSH Hartley Sweeten }
578057c86dSH Hartley Sweeten 
m48t86_writeb(struct device * dev,unsigned char value,unsigned long addr)588057c86dSH Hartley Sweeten static void m48t86_writeb(struct device *dev,
598057c86dSH Hartley Sweeten 			  unsigned char value, unsigned long addr)
608057c86dSH Hartley Sweeten {
618057c86dSH Hartley Sweeten 	struct m48t86_rtc_info *info = dev_get_drvdata(dev);
628057c86dSH Hartley Sweeten 
638057c86dSH Hartley Sweeten 	writeb(addr, info->index_reg);
648057c86dSH Hartley Sweeten 	writeb(value, info->data_reg);
658057c86dSH Hartley Sweeten }
668057c86dSH Hartley Sweeten 
m48t86_rtc_read_time(struct device * dev,struct rtc_time * tm)671d98af87SAlessandro Zummo static int m48t86_rtc_read_time(struct device *dev, struct rtc_time *tm)
681d98af87SAlessandro Zummo {
691d98af87SAlessandro Zummo 	unsigned char reg;
701d98af87SAlessandro Zummo 
718057c86dSH Hartley Sweeten 	reg = m48t86_readb(dev, M48T86_B);
721d98af87SAlessandro Zummo 
7368b54f47SH Hartley Sweeten 	if (reg & M48T86_B_DM) {
741d98af87SAlessandro Zummo 		/* data (binary) mode */
758057c86dSH Hartley Sweeten 		tm->tm_sec	= m48t86_readb(dev, M48T86_SEC);
768057c86dSH Hartley Sweeten 		tm->tm_min	= m48t86_readb(dev, M48T86_MIN);
778057c86dSH Hartley Sweeten 		tm->tm_hour	= m48t86_readb(dev, M48T86_HOUR) & 0x3f;
788057c86dSH Hartley Sweeten 		tm->tm_mday	= m48t86_readb(dev, M48T86_DOM);
791d98af87SAlessandro Zummo 		/* tm_mon is 0-11 */
808057c86dSH Hartley Sweeten 		tm->tm_mon	= m48t86_readb(dev, M48T86_MONTH) - 1;
818057c86dSH Hartley Sweeten 		tm->tm_year	= m48t86_readb(dev, M48T86_YEAR) + 100;
828057c86dSH Hartley Sweeten 		tm->tm_wday	= m48t86_readb(dev, M48T86_DOW);
831d98af87SAlessandro Zummo 	} else {
841d98af87SAlessandro Zummo 		/* bcd mode */
858057c86dSH Hartley Sweeten 		tm->tm_sec	= bcd2bin(m48t86_readb(dev, M48T86_SEC));
868057c86dSH Hartley Sweeten 		tm->tm_min	= bcd2bin(m48t86_readb(dev, M48T86_MIN));
878057c86dSH Hartley Sweeten 		tm->tm_hour	= bcd2bin(m48t86_readb(dev, M48T86_HOUR) &
888057c86dSH Hartley Sweeten 					  0x3f);
898057c86dSH Hartley Sweeten 		tm->tm_mday	= bcd2bin(m48t86_readb(dev, M48T86_DOM));
901d98af87SAlessandro Zummo 		/* tm_mon is 0-11 */
918057c86dSH Hartley Sweeten 		tm->tm_mon	= bcd2bin(m48t86_readb(dev, M48T86_MONTH)) - 1;
928057c86dSH Hartley Sweeten 		tm->tm_year	= bcd2bin(m48t86_readb(dev, M48T86_YEAR)) + 100;
938057c86dSH Hartley Sweeten 		tm->tm_wday	= bcd2bin(m48t86_readb(dev, M48T86_DOW));
941d98af87SAlessandro Zummo 	}
951d98af87SAlessandro Zummo 
961d98af87SAlessandro Zummo 	/* correct the hour if the clock is in 12h mode */
9768b54f47SH Hartley Sweeten 	if (!(reg & M48T86_B_H24))
988057c86dSH Hartley Sweeten 		if (m48t86_readb(dev, M48T86_HOUR) & 0x80)
991d98af87SAlessandro Zummo 			tm->tm_hour += 12;
1001d98af87SAlessandro Zummo 
10122652ba7SAlexandre Belloni 	return 0;
1021d98af87SAlessandro Zummo }
1031d98af87SAlessandro Zummo 
m48t86_rtc_set_time(struct device * dev,struct rtc_time * tm)1041d98af87SAlessandro Zummo static int m48t86_rtc_set_time(struct device *dev, struct rtc_time *tm)
1051d98af87SAlessandro Zummo {
1061d98af87SAlessandro Zummo 	unsigned char reg;
1071d98af87SAlessandro Zummo 
1088057c86dSH Hartley Sweeten 	reg = m48t86_readb(dev, M48T86_B);
1091d98af87SAlessandro Zummo 
1101d98af87SAlessandro Zummo 	/* update flag and 24h mode */
11168b54f47SH Hartley Sweeten 	reg |= M48T86_B_SET | M48T86_B_H24;
1128057c86dSH Hartley Sweeten 	m48t86_writeb(dev, reg, M48T86_B);
1131d98af87SAlessandro Zummo 
11468b54f47SH Hartley Sweeten 	if (reg & M48T86_B_DM) {
1151d98af87SAlessandro Zummo 		/* data (binary) mode */
1168057c86dSH Hartley Sweeten 		m48t86_writeb(dev, tm->tm_sec, M48T86_SEC);
1178057c86dSH Hartley Sweeten 		m48t86_writeb(dev, tm->tm_min, M48T86_MIN);
1188057c86dSH Hartley Sweeten 		m48t86_writeb(dev, tm->tm_hour, M48T86_HOUR);
1198057c86dSH Hartley Sweeten 		m48t86_writeb(dev, tm->tm_mday, M48T86_DOM);
1208057c86dSH Hartley Sweeten 		m48t86_writeb(dev, tm->tm_mon + 1, M48T86_MONTH);
1218057c86dSH Hartley Sweeten 		m48t86_writeb(dev, tm->tm_year % 100, M48T86_YEAR);
1228057c86dSH Hartley Sweeten 		m48t86_writeb(dev, tm->tm_wday, M48T86_DOW);
1231d98af87SAlessandro Zummo 	} else {
1241d98af87SAlessandro Zummo 		/* bcd mode */
1258057c86dSH Hartley Sweeten 		m48t86_writeb(dev, bin2bcd(tm->tm_sec), M48T86_SEC);
1268057c86dSH Hartley Sweeten 		m48t86_writeb(dev, bin2bcd(tm->tm_min), M48T86_MIN);
1278057c86dSH Hartley Sweeten 		m48t86_writeb(dev, bin2bcd(tm->tm_hour), M48T86_HOUR);
1288057c86dSH Hartley Sweeten 		m48t86_writeb(dev, bin2bcd(tm->tm_mday), M48T86_DOM);
1298057c86dSH Hartley Sweeten 		m48t86_writeb(dev, bin2bcd(tm->tm_mon + 1), M48T86_MONTH);
1308057c86dSH Hartley Sweeten 		m48t86_writeb(dev, bin2bcd(tm->tm_year % 100), M48T86_YEAR);
1318057c86dSH Hartley Sweeten 		m48t86_writeb(dev, bin2bcd(tm->tm_wday), M48T86_DOW);
1321d98af87SAlessandro Zummo 	}
1331d98af87SAlessandro Zummo 
1341d98af87SAlessandro Zummo 	/* update ended */
13568b54f47SH Hartley Sweeten 	reg &= ~M48T86_B_SET;
1368057c86dSH Hartley Sweeten 	m48t86_writeb(dev, reg, M48T86_B);
1371d98af87SAlessandro Zummo 
1381d98af87SAlessandro Zummo 	return 0;
1391d98af87SAlessandro Zummo }
1401d98af87SAlessandro Zummo 
m48t86_rtc_proc(struct device * dev,struct seq_file * seq)1411d98af87SAlessandro Zummo static int m48t86_rtc_proc(struct device *dev, struct seq_file *seq)
1421d98af87SAlessandro Zummo {
1431d98af87SAlessandro Zummo 	unsigned char reg;
1441d98af87SAlessandro Zummo 
1458057c86dSH Hartley Sweeten 	reg = m48t86_readb(dev, M48T86_B);
1461d98af87SAlessandro Zummo 
1471d98af87SAlessandro Zummo 	seq_printf(seq, "mode\t\t: %s\n",
14868b54f47SH Hartley Sweeten 		   (reg & M48T86_B_DM) ? "binary" : "bcd");
1491d98af87SAlessandro Zummo 
1508057c86dSH Hartley Sweeten 	reg = m48t86_readb(dev, M48T86_D);
1511d98af87SAlessandro Zummo 
1521d98af87SAlessandro Zummo 	seq_printf(seq, "battery\t\t: %s\n",
15368b54f47SH Hartley Sweeten 		   (reg & M48T86_D_VRT) ? "ok" : "exhausted");
1541d98af87SAlessandro Zummo 
1551d98af87SAlessandro Zummo 	return 0;
1561d98af87SAlessandro Zummo }
1571d98af87SAlessandro Zummo 
158ff8371acSDavid Brownell static const struct rtc_class_ops m48t86_rtc_ops = {
1591d98af87SAlessandro Zummo 	.read_time	= m48t86_rtc_read_time,
1601d98af87SAlessandro Zummo 	.set_time	= m48t86_rtc_set_time,
1611d98af87SAlessandro Zummo 	.proc		= m48t86_rtc_proc,
1621d98af87SAlessandro Zummo };
1631d98af87SAlessandro Zummo 
m48t86_nvram_read(void * priv,unsigned int off,void * buf,size_t count)164f8033aabSAlexandre Belloni static int m48t86_nvram_read(void *priv, unsigned int off, void *buf,
165f8033aabSAlexandre Belloni 			     size_t count)
166b180cf8bSH Hartley Sweeten {
167f8033aabSAlexandre Belloni 	struct device *dev = priv;
168b180cf8bSH Hartley Sweeten 	unsigned int i;
169b180cf8bSH Hartley Sweeten 
170b180cf8bSH Hartley Sweeten 	for (i = 0; i < count; i++)
171f8033aabSAlexandre Belloni 		((u8 *)buf)[i] = m48t86_readb(dev, M48T86_NVRAM(off + i));
172b180cf8bSH Hartley Sweeten 
173f8033aabSAlexandre Belloni 	return 0;
174b180cf8bSH Hartley Sweeten }
175b180cf8bSH Hartley Sweeten 
m48t86_nvram_write(void * priv,unsigned int off,void * buf,size_t count)176f8033aabSAlexandre Belloni static int m48t86_nvram_write(void *priv, unsigned int off, void *buf,
177f8033aabSAlexandre Belloni 			      size_t count)
178b180cf8bSH Hartley Sweeten {
179f8033aabSAlexandre Belloni 	struct device *dev = priv;
180b180cf8bSH Hartley Sweeten 	unsigned int i;
181b180cf8bSH Hartley Sweeten 
182b180cf8bSH Hartley Sweeten 	for (i = 0; i < count; i++)
183f8033aabSAlexandre Belloni 		m48t86_writeb(dev, ((u8 *)buf)[i], M48T86_NVRAM(off + i));
184b180cf8bSH Hartley Sweeten 
185f8033aabSAlexandre Belloni 	return 0;
186b180cf8bSH Hartley Sweeten }
187b180cf8bSH Hartley Sweeten 
1883ea07127SH Hartley Sweeten /*
1893ea07127SH Hartley Sweeten  * The RTC is an optional feature at purchase time on some Technologic Systems
1903ea07127SH Hartley Sweeten  * boards. Verify that it actually exists by checking if the last two bytes
1913ea07127SH Hartley Sweeten  * of the NVRAM can be changed.
1923ea07127SH Hartley Sweeten  *
1933ea07127SH Hartley Sweeten  * This is based on the method used in their rtc7800.c example.
1943ea07127SH Hartley Sweeten  */
m48t86_verify_chip(struct platform_device * pdev)1953ea07127SH Hartley Sweeten static bool m48t86_verify_chip(struct platform_device *pdev)
1963ea07127SH Hartley Sweeten {
1973ea07127SH Hartley Sweeten 	unsigned int offset0 = M48T86_NVRAM(M48T86_NVRAM_LEN - 2);
1983ea07127SH Hartley Sweeten 	unsigned int offset1 = M48T86_NVRAM(M48T86_NVRAM_LEN - 1);
1993ea07127SH Hartley Sweeten 	unsigned char tmp0, tmp1;
2003ea07127SH Hartley Sweeten 
2013ea07127SH Hartley Sweeten 	tmp0 = m48t86_readb(&pdev->dev, offset0);
2023ea07127SH Hartley Sweeten 	tmp1 = m48t86_readb(&pdev->dev, offset1);
2033ea07127SH Hartley Sweeten 
2043ea07127SH Hartley Sweeten 	m48t86_writeb(&pdev->dev, 0x00, offset0);
2053ea07127SH Hartley Sweeten 	m48t86_writeb(&pdev->dev, 0x55, offset1);
2063ea07127SH Hartley Sweeten 	if (m48t86_readb(&pdev->dev, offset1) == 0x55) {
2073ea07127SH Hartley Sweeten 		m48t86_writeb(&pdev->dev, 0xaa, offset1);
2083ea07127SH Hartley Sweeten 		if (m48t86_readb(&pdev->dev, offset1) == 0xaa &&
2093ea07127SH Hartley Sweeten 		    m48t86_readb(&pdev->dev, offset0) == 0x00) {
2103ea07127SH Hartley Sweeten 			m48t86_writeb(&pdev->dev, tmp0, offset0);
2113ea07127SH Hartley Sweeten 			m48t86_writeb(&pdev->dev, tmp1, offset1);
2123ea07127SH Hartley Sweeten 
2133ea07127SH Hartley Sweeten 			return true;
2143ea07127SH Hartley Sweeten 		}
2153ea07127SH Hartley Sweeten 	}
2163ea07127SH Hartley Sweeten 	return false;
2173ea07127SH Hartley Sweeten }
2183ea07127SH Hartley Sweeten 
m48t86_rtc_probe(struct platform_device * pdev)2198057c86dSH Hartley Sweeten static int m48t86_rtc_probe(struct platform_device *pdev)
2201d98af87SAlessandro Zummo {
2218057c86dSH Hartley Sweeten 	struct m48t86_rtc_info *info;
2221d98af87SAlessandro Zummo 	unsigned char reg;
2235508c725SAlexandre Belloni 	int err;
224e3f51c0dSAlexandre Belloni 	struct nvmem_config m48t86_nvmem_cfg = {
225e3f51c0dSAlexandre Belloni 		.name = "m48t86_nvram",
226e3f51c0dSAlexandre Belloni 		.word_size = 1,
227e3f51c0dSAlexandre Belloni 		.stride = 1,
228e3f51c0dSAlexandre Belloni 		.size = M48T86_NVRAM_LEN,
229e3f51c0dSAlexandre Belloni 		.reg_read = m48t86_nvram_read,
230e3f51c0dSAlexandre Belloni 		.reg_write = m48t86_nvram_write,
231e3f51c0dSAlexandre Belloni 		.priv = &pdev->dev,
232e3f51c0dSAlexandre Belloni 	};
233d5b6bb0aSJingoo Han 
2348057c86dSH Hartley Sweeten 	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
2358057c86dSH Hartley Sweeten 	if (!info)
2368057c86dSH Hartley Sweeten 		return -ENOMEM;
2378057c86dSH Hartley Sweeten 
23889576bebSMarkus Elfring 	info->index_reg = devm_platform_ioremap_resource(pdev, 0);
2398057c86dSH Hartley Sweeten 	if (IS_ERR(info->index_reg))
2408057c86dSH Hartley Sweeten 		return PTR_ERR(info->index_reg);
2418057c86dSH Hartley Sweeten 
24289576bebSMarkus Elfring 	info->data_reg = devm_platform_ioremap_resource(pdev, 1);
2438057c86dSH Hartley Sweeten 	if (IS_ERR(info->data_reg))
2448057c86dSH Hartley Sweeten 		return PTR_ERR(info->data_reg);
2458057c86dSH Hartley Sweeten 
2468057c86dSH Hartley Sweeten 	dev_set_drvdata(&pdev->dev, info);
2478057c86dSH Hartley Sweeten 
2483ea07127SH Hartley Sweeten 	if (!m48t86_verify_chip(pdev)) {
2493ea07127SH Hartley Sweeten 		dev_info(&pdev->dev, "RTC not present\n");
2503ea07127SH Hartley Sweeten 		return -ENODEV;
2513ea07127SH Hartley Sweeten 	}
2523ea07127SH Hartley Sweeten 
2535508c725SAlexandre Belloni 	info->rtc = devm_rtc_allocate_device(&pdev->dev);
2548057c86dSH Hartley Sweeten 	if (IS_ERR(info->rtc))
2558057c86dSH Hartley Sweeten 		return PTR_ERR(info->rtc);
2561d98af87SAlessandro Zummo 
2575508c725SAlexandre Belloni 	info->rtc->ops = &m48t86_rtc_ops;
258f8033aabSAlexandre Belloni 
259fdcfd854SBartosz Golaszewski 	err = devm_rtc_register_device(info->rtc);
2605508c725SAlexandre Belloni 	if (err)
2615508c725SAlexandre Belloni 		return err;
2625508c725SAlexandre Belloni 
2633a905c2dSBartosz Golaszewski 	devm_rtc_nvmem_register(info->rtc, &m48t86_nvmem_cfg);
2643c1bb61fSAlexandre Belloni 
2651d98af87SAlessandro Zummo 	/* read battery status */
2668057c86dSH Hartley Sweeten 	reg = m48t86_readb(&pdev->dev, M48T86_D);
2678057c86dSH Hartley Sweeten 	dev_info(&pdev->dev, "battery %s\n",
26868b54f47SH Hartley Sweeten 		 (reg & M48T86_D_VRT) ? "ok" : "exhausted");
2691d98af87SAlessandro Zummo 
2701d98af87SAlessandro Zummo 	return 0;
2711d98af87SAlessandro Zummo }
2721d98af87SAlessandro Zummo 
273*6ec3f5ecSNikita Shubin static const struct of_device_id m48t86_rtc_of_ids[] = {
274*6ec3f5ecSNikita Shubin 	{ .compatible = "st,m48t86" },
275*6ec3f5ecSNikita Shubin 	{ /* sentinel */ }
276*6ec3f5ecSNikita Shubin };
277*6ec3f5ecSNikita Shubin MODULE_DEVICE_TABLE(of, m48t86_rtc_of_ids);
278*6ec3f5ecSNikita Shubin 
2791d98af87SAlessandro Zummo static struct platform_driver m48t86_rtc_platform_driver = {
2801d98af87SAlessandro Zummo 	.driver		= {
2811d98af87SAlessandro Zummo 		.name	= "rtc-m48t86",
282*6ec3f5ecSNikita Shubin 		.of_match_table = m48t86_rtc_of_ids,
2831d98af87SAlessandro Zummo 	},
2841d98af87SAlessandro Zummo 	.probe		= m48t86_rtc_probe,
2851d98af87SAlessandro Zummo };
2861d98af87SAlessandro Zummo 
2870c4eae66SAxel Lin module_platform_driver(m48t86_rtc_platform_driver);
2881d98af87SAlessandro Zummo 
2891d98af87SAlessandro Zummo MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");
2901d98af87SAlessandro Zummo MODULE_DESCRIPTION("M48T86 RTC driver");
2911d98af87SAlessandro Zummo MODULE_LICENSE("GPL");
292ad28a07bSKay Sievers MODULE_ALIAS("platform:rtc-m48t86");
293