12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2d1dbd82eSThomas Bogendoerfer /*
3d1dbd82eSThomas Bogendoerfer * Driver for the SGS-Thomson M48T35 Timekeeper RAM chip
4d1dbd82eSThomas Bogendoerfer *
5d1dbd82eSThomas Bogendoerfer * Copyright (C) 2000 Silicon Graphics, Inc.
6d1dbd82eSThomas Bogendoerfer * Written by Ulf Carlsson (ulfc@engr.sgi.com)
7d1dbd82eSThomas Bogendoerfer *
8d1dbd82eSThomas Bogendoerfer * Copyright (C) 2008 Thomas Bogendoerfer
9d1dbd82eSThomas Bogendoerfer *
10d1dbd82eSThomas Bogendoerfer * Based on code written by Paul Gortmaker.
11d1dbd82eSThomas Bogendoerfer */
12d1dbd82eSThomas Bogendoerfer
13d1dbd82eSThomas Bogendoerfer #include <linux/module.h>
14d1dbd82eSThomas Bogendoerfer #include <linux/rtc.h>
155a0e3ad6STejun Heo #include <linux/slab.h>
16d1dbd82eSThomas Bogendoerfer #include <linux/platform_device.h>
17d1dbd82eSThomas Bogendoerfer #include <linux/bcd.h>
18d7a6119fSGeert Uytterhoeven #include <linux/io.h>
198ff7b7d9SSachin Kamat #include <linux/err.h>
20d1dbd82eSThomas Bogendoerfer
21d1dbd82eSThomas Bogendoerfer struct m48t35_rtc {
22d1dbd82eSThomas Bogendoerfer u8 pad[0x7ff8]; /* starts at 0x7ff8 */
2310cf8300SThomas Bogendoerfer #ifdef CONFIG_SGI_IP27
2410cf8300SThomas Bogendoerfer u8 hour;
2510cf8300SThomas Bogendoerfer u8 min;
2610cf8300SThomas Bogendoerfer u8 sec;
2710cf8300SThomas Bogendoerfer u8 control;
2810cf8300SThomas Bogendoerfer u8 year;
2910cf8300SThomas Bogendoerfer u8 month;
3010cf8300SThomas Bogendoerfer u8 date;
3110cf8300SThomas Bogendoerfer u8 day;
3210cf8300SThomas Bogendoerfer #else
33d1dbd82eSThomas Bogendoerfer u8 control;
34d1dbd82eSThomas Bogendoerfer u8 sec;
35d1dbd82eSThomas Bogendoerfer u8 min;
36d1dbd82eSThomas Bogendoerfer u8 hour;
37d1dbd82eSThomas Bogendoerfer u8 day;
38d1dbd82eSThomas Bogendoerfer u8 date;
39d1dbd82eSThomas Bogendoerfer u8 month;
40d1dbd82eSThomas Bogendoerfer u8 year;
4110cf8300SThomas Bogendoerfer #endif
42d1dbd82eSThomas Bogendoerfer };
43d1dbd82eSThomas Bogendoerfer
44d1dbd82eSThomas Bogendoerfer #define M48T35_RTC_SET 0x80
45d1dbd82eSThomas Bogendoerfer #define M48T35_RTC_READ 0x40
46d1dbd82eSThomas Bogendoerfer
47d1dbd82eSThomas Bogendoerfer struct m48t35_priv {
48d1dbd82eSThomas Bogendoerfer struct rtc_device *rtc;
49d1dbd82eSThomas Bogendoerfer struct m48t35_rtc __iomem *reg;
50d1dbd82eSThomas Bogendoerfer size_t size;
51d1dbd82eSThomas Bogendoerfer unsigned long baseaddr;
52d1dbd82eSThomas Bogendoerfer spinlock_t lock;
53d1dbd82eSThomas Bogendoerfer };
54d1dbd82eSThomas Bogendoerfer
m48t35_read_time(struct device * dev,struct rtc_time * tm)55d1dbd82eSThomas Bogendoerfer static int m48t35_read_time(struct device *dev, struct rtc_time *tm)
56d1dbd82eSThomas Bogendoerfer {
57d1dbd82eSThomas Bogendoerfer struct m48t35_priv *priv = dev_get_drvdata(dev);
58d1dbd82eSThomas Bogendoerfer u8 control;
59d1dbd82eSThomas Bogendoerfer
60d1dbd82eSThomas Bogendoerfer /*
61d1dbd82eSThomas Bogendoerfer * Only the values that we read from the RTC are set. We leave
62d1dbd82eSThomas Bogendoerfer * tm_wday, tm_yday and tm_isdst untouched. Even though the
63d1dbd82eSThomas Bogendoerfer * RTC has RTC_DAY_OF_WEEK, we ignore it, as it is only updated
64d1dbd82eSThomas Bogendoerfer * by the RTC when initially set to a non-zero value.
65d1dbd82eSThomas Bogendoerfer */
66d1dbd82eSThomas Bogendoerfer spin_lock_irq(&priv->lock);
67d1dbd82eSThomas Bogendoerfer control = readb(&priv->reg->control);
68d1dbd82eSThomas Bogendoerfer writeb(control | M48T35_RTC_READ, &priv->reg->control);
69d1dbd82eSThomas Bogendoerfer tm->tm_sec = readb(&priv->reg->sec);
70d1dbd82eSThomas Bogendoerfer tm->tm_min = readb(&priv->reg->min);
71d1dbd82eSThomas Bogendoerfer tm->tm_hour = readb(&priv->reg->hour);
72d1dbd82eSThomas Bogendoerfer tm->tm_mday = readb(&priv->reg->date);
73d1dbd82eSThomas Bogendoerfer tm->tm_mon = readb(&priv->reg->month);
74d1dbd82eSThomas Bogendoerfer tm->tm_year = readb(&priv->reg->year);
75d1dbd82eSThomas Bogendoerfer writeb(control, &priv->reg->control);
76d1dbd82eSThomas Bogendoerfer spin_unlock_irq(&priv->lock);
77d1dbd82eSThomas Bogendoerfer
78d1dbd82eSThomas Bogendoerfer tm->tm_sec = bcd2bin(tm->tm_sec);
79d1dbd82eSThomas Bogendoerfer tm->tm_min = bcd2bin(tm->tm_min);
80d1dbd82eSThomas Bogendoerfer tm->tm_hour = bcd2bin(tm->tm_hour);
81d1dbd82eSThomas Bogendoerfer tm->tm_mday = bcd2bin(tm->tm_mday);
82d1dbd82eSThomas Bogendoerfer tm->tm_mon = bcd2bin(tm->tm_mon);
83d1dbd82eSThomas Bogendoerfer tm->tm_year = bcd2bin(tm->tm_year);
84d1dbd82eSThomas Bogendoerfer
85d1dbd82eSThomas Bogendoerfer /*
86d1dbd82eSThomas Bogendoerfer * Account for differences between how the RTC uses the values
87d1dbd82eSThomas Bogendoerfer * and how they are defined in a struct rtc_time;
88d1dbd82eSThomas Bogendoerfer */
89d1dbd82eSThomas Bogendoerfer tm->tm_year += 70;
90d1dbd82eSThomas Bogendoerfer if (tm->tm_year <= 69)
91d1dbd82eSThomas Bogendoerfer tm->tm_year += 100;
92d1dbd82eSThomas Bogendoerfer
93d1dbd82eSThomas Bogendoerfer tm->tm_mon--;
9422652ba7SAlexandre Belloni return 0;
95d1dbd82eSThomas Bogendoerfer }
96d1dbd82eSThomas Bogendoerfer
m48t35_set_time(struct device * dev,struct rtc_time * tm)97d1dbd82eSThomas Bogendoerfer static int m48t35_set_time(struct device *dev, struct rtc_time *tm)
98d1dbd82eSThomas Bogendoerfer {
99d1dbd82eSThomas Bogendoerfer struct m48t35_priv *priv = dev_get_drvdata(dev);
100d1dbd82eSThomas Bogendoerfer unsigned char mon, day, hrs, min, sec;
101d1dbd82eSThomas Bogendoerfer unsigned int yrs;
102d1dbd82eSThomas Bogendoerfer u8 control;
103d1dbd82eSThomas Bogendoerfer
104d1dbd82eSThomas Bogendoerfer yrs = tm->tm_year + 1900;
105d1dbd82eSThomas Bogendoerfer mon = tm->tm_mon + 1; /* tm_mon starts at zero */
106d1dbd82eSThomas Bogendoerfer day = tm->tm_mday;
107d1dbd82eSThomas Bogendoerfer hrs = tm->tm_hour;
108d1dbd82eSThomas Bogendoerfer min = tm->tm_min;
109d1dbd82eSThomas Bogendoerfer sec = tm->tm_sec;
110d1dbd82eSThomas Bogendoerfer
111d1dbd82eSThomas Bogendoerfer if (yrs < 1970)
112d1dbd82eSThomas Bogendoerfer return -EINVAL;
113d1dbd82eSThomas Bogendoerfer
114d1dbd82eSThomas Bogendoerfer yrs -= 1970;
115d1dbd82eSThomas Bogendoerfer if (yrs > 255) /* They are unsigned */
116d1dbd82eSThomas Bogendoerfer return -EINVAL;
117d1dbd82eSThomas Bogendoerfer
118d1dbd82eSThomas Bogendoerfer if (yrs > 169)
119d1dbd82eSThomas Bogendoerfer return -EINVAL;
120d1dbd82eSThomas Bogendoerfer
121d1dbd82eSThomas Bogendoerfer if (yrs >= 100)
122d1dbd82eSThomas Bogendoerfer yrs -= 100;
123d1dbd82eSThomas Bogendoerfer
124d1dbd82eSThomas Bogendoerfer sec = bin2bcd(sec);
125d1dbd82eSThomas Bogendoerfer min = bin2bcd(min);
126d1dbd82eSThomas Bogendoerfer hrs = bin2bcd(hrs);
127d1dbd82eSThomas Bogendoerfer day = bin2bcd(day);
128d1dbd82eSThomas Bogendoerfer mon = bin2bcd(mon);
129d1dbd82eSThomas Bogendoerfer yrs = bin2bcd(yrs);
130d1dbd82eSThomas Bogendoerfer
131d1dbd82eSThomas Bogendoerfer spin_lock_irq(&priv->lock);
132d1dbd82eSThomas Bogendoerfer control = readb(&priv->reg->control);
133d1dbd82eSThomas Bogendoerfer writeb(control | M48T35_RTC_SET, &priv->reg->control);
134d1dbd82eSThomas Bogendoerfer writeb(yrs, &priv->reg->year);
135d1dbd82eSThomas Bogendoerfer writeb(mon, &priv->reg->month);
136d1dbd82eSThomas Bogendoerfer writeb(day, &priv->reg->date);
137d1dbd82eSThomas Bogendoerfer writeb(hrs, &priv->reg->hour);
138d1dbd82eSThomas Bogendoerfer writeb(min, &priv->reg->min);
139d1dbd82eSThomas Bogendoerfer writeb(sec, &priv->reg->sec);
140d1dbd82eSThomas Bogendoerfer writeb(control, &priv->reg->control);
141d1dbd82eSThomas Bogendoerfer spin_unlock_irq(&priv->lock);
142d1dbd82eSThomas Bogendoerfer return 0;
143d1dbd82eSThomas Bogendoerfer }
144d1dbd82eSThomas Bogendoerfer
145d1dbd82eSThomas Bogendoerfer static const struct rtc_class_ops m48t35_ops = {
146d1dbd82eSThomas Bogendoerfer .read_time = m48t35_read_time,
147d1dbd82eSThomas Bogendoerfer .set_time = m48t35_set_time,
148d1dbd82eSThomas Bogendoerfer };
149d1dbd82eSThomas Bogendoerfer
m48t35_probe(struct platform_device * pdev)1505a167f45SGreg Kroah-Hartman static int m48t35_probe(struct platform_device *pdev)
151d1dbd82eSThomas Bogendoerfer {
152d1dbd82eSThomas Bogendoerfer struct resource *res;
153d1dbd82eSThomas Bogendoerfer struct m48t35_priv *priv;
154d1dbd82eSThomas Bogendoerfer
155d1dbd82eSThomas Bogendoerfer res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
156d1dbd82eSThomas Bogendoerfer if (!res)
157d1dbd82eSThomas Bogendoerfer return -ENODEV;
1584f58cd9bSSachin Kamat priv = devm_kzalloc(&pdev->dev, sizeof(struct m48t35_priv), GFP_KERNEL);
159d1dbd82eSThomas Bogendoerfer if (!priv)
160d1dbd82eSThomas Bogendoerfer return -ENOMEM;
161d1dbd82eSThomas Bogendoerfer
16228f65c11SJoe Perches priv->size = resource_size(res);
1634f58cd9bSSachin Kamat if (!devm_request_mem_region(&pdev->dev, res->start, priv->size,
1644f58cd9bSSachin Kamat pdev->name))
1654f58cd9bSSachin Kamat return -EBUSY;
166*eac1c3fcSThomas Bogendoerfer
167d1dbd82eSThomas Bogendoerfer priv->baseaddr = res->start;
1684f58cd9bSSachin Kamat priv->reg = devm_ioremap(&pdev->dev, priv->baseaddr, priv->size);
1694f58cd9bSSachin Kamat if (!priv->reg)
1704f58cd9bSSachin Kamat return -ENOMEM;
171b74d2caaSAlessandro Zummo
172d1dbd82eSThomas Bogendoerfer spin_lock_init(&priv->lock);
173b74d2caaSAlessandro Zummo
174b74d2caaSAlessandro Zummo platform_set_drvdata(pdev, priv);
175b74d2caaSAlessandro Zummo
1764f58cd9bSSachin Kamat priv->rtc = devm_rtc_device_register(&pdev->dev, "m48t35",
177d1dbd82eSThomas Bogendoerfer &m48t35_ops, THIS_MODULE);
178dac30a98SSachin Kamat return PTR_ERR_OR_ZERO(priv->rtc);
179d1dbd82eSThomas Bogendoerfer }
180d1dbd82eSThomas Bogendoerfer
181d1dbd82eSThomas Bogendoerfer static struct platform_driver m48t35_platform_driver = {
182d1dbd82eSThomas Bogendoerfer .driver = {
183d1dbd82eSThomas Bogendoerfer .name = "rtc-m48t35",
184d1dbd82eSThomas Bogendoerfer },
185d1dbd82eSThomas Bogendoerfer .probe = m48t35_probe,
186d1dbd82eSThomas Bogendoerfer };
187d1dbd82eSThomas Bogendoerfer
1880c4eae66SAxel Lin module_platform_driver(m48t35_platform_driver);
189d1dbd82eSThomas Bogendoerfer
190d1dbd82eSThomas Bogendoerfer MODULE_AUTHOR("Thomas Bogendoerfer <tsbogend@alpha.franken.de>");
191d1dbd82eSThomas Bogendoerfer MODULE_DESCRIPTION("M48T35 RTC driver");
192d1dbd82eSThomas Bogendoerfer MODULE_LICENSE("GPL");
193d1dbd82eSThomas Bogendoerfer MODULE_ALIAS("platform:rtc-m48t35");
194