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