293 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * ST M48T86 / Dallas DS12887 RTC driver4 * Copyright (c) 2006 Tower Technologies5 *6 * Author: Alessandro Zummo <a.zummo@towertech.it>7 *8 * This drivers only supports the clock running in BCD and 24H mode.9 * If it will be ever adapted to binary and 12H mode, care must be taken10 * to not introduce bugs.11 */12 13#include <linux/module.h>14#include <linux/mod_devicetable.h>15#include <linux/rtc.h>16#include <linux/platform_device.h>17#include <linux/bcd.h>18#include <linux/io.h>19 20#define M48T86_SEC 0x0021#define M48T86_SECALRM 0x0122#define M48T86_MIN 0x0223#define M48T86_MINALRM 0x0324#define M48T86_HOUR 0x0425#define M48T86_HOURALRM 0x0526#define M48T86_DOW 0x06 /* 1 = sunday */27#define M48T86_DOM 0x0728#define M48T86_MONTH 0x08 /* 1 - 12 */29#define M48T86_YEAR 0x09 /* 0 - 99 */30#define M48T86_A 0x0a31#define M48T86_B 0x0b32#define M48T86_B_SET BIT(7)33#define M48T86_B_DM BIT(2)34#define M48T86_B_H24 BIT(1)35#define M48T86_C 0x0c36#define M48T86_D 0x0d37#define M48T86_D_VRT BIT(7)38#define M48T86_NVRAM(x) (0x0e + (x))39#define M48T86_NVRAM_LEN 11440 41struct m48t86_rtc_info {42 void __iomem *index_reg;43 void __iomem *data_reg;44 struct rtc_device *rtc;45};46 47static unsigned char m48t86_readb(struct device *dev, unsigned long addr)48{49 struct m48t86_rtc_info *info = dev_get_drvdata(dev);50 unsigned char value;51 52 writeb(addr, info->index_reg);53 value = readb(info->data_reg);54 55 return value;56}57 58static void m48t86_writeb(struct device *dev,59 unsigned char value, unsigned long addr)60{61 struct m48t86_rtc_info *info = dev_get_drvdata(dev);62 63 writeb(addr, info->index_reg);64 writeb(value, info->data_reg);65}66 67static int m48t86_rtc_read_time(struct device *dev, struct rtc_time *tm)68{69 unsigned char reg;70 71 reg = m48t86_readb(dev, M48T86_B);72 73 if (reg & M48T86_B_DM) {74 /* data (binary) mode */75 tm->tm_sec = m48t86_readb(dev, M48T86_SEC);76 tm->tm_min = m48t86_readb(dev, M48T86_MIN);77 tm->tm_hour = m48t86_readb(dev, M48T86_HOUR) & 0x3f;78 tm->tm_mday = m48t86_readb(dev, M48T86_DOM);79 /* tm_mon is 0-11 */80 tm->tm_mon = m48t86_readb(dev, M48T86_MONTH) - 1;81 tm->tm_year = m48t86_readb(dev, M48T86_YEAR) + 100;82 tm->tm_wday = m48t86_readb(dev, M48T86_DOW);83 } else {84 /* bcd mode */85 tm->tm_sec = bcd2bin(m48t86_readb(dev, M48T86_SEC));86 tm->tm_min = bcd2bin(m48t86_readb(dev, M48T86_MIN));87 tm->tm_hour = bcd2bin(m48t86_readb(dev, M48T86_HOUR) &88 0x3f);89 tm->tm_mday = bcd2bin(m48t86_readb(dev, M48T86_DOM));90 /* tm_mon is 0-11 */91 tm->tm_mon = bcd2bin(m48t86_readb(dev, M48T86_MONTH)) - 1;92 tm->tm_year = bcd2bin(m48t86_readb(dev, M48T86_YEAR)) + 100;93 tm->tm_wday = bcd2bin(m48t86_readb(dev, M48T86_DOW));94 }95 96 /* correct the hour if the clock is in 12h mode */97 if (!(reg & M48T86_B_H24))98 if (m48t86_readb(dev, M48T86_HOUR) & 0x80)99 tm->tm_hour += 12;100 101 return 0;102}103 104static int m48t86_rtc_set_time(struct device *dev, struct rtc_time *tm)105{106 unsigned char reg;107 108 reg = m48t86_readb(dev, M48T86_B);109 110 /* update flag and 24h mode */111 reg |= M48T86_B_SET | M48T86_B_H24;112 m48t86_writeb(dev, reg, M48T86_B);113 114 if (reg & M48T86_B_DM) {115 /* data (binary) mode */116 m48t86_writeb(dev, tm->tm_sec, M48T86_SEC);117 m48t86_writeb(dev, tm->tm_min, M48T86_MIN);118 m48t86_writeb(dev, tm->tm_hour, M48T86_HOUR);119 m48t86_writeb(dev, tm->tm_mday, M48T86_DOM);120 m48t86_writeb(dev, tm->tm_mon + 1, M48T86_MONTH);121 m48t86_writeb(dev, tm->tm_year % 100, M48T86_YEAR);122 m48t86_writeb(dev, tm->tm_wday, M48T86_DOW);123 } else {124 /* bcd mode */125 m48t86_writeb(dev, bin2bcd(tm->tm_sec), M48T86_SEC);126 m48t86_writeb(dev, bin2bcd(tm->tm_min), M48T86_MIN);127 m48t86_writeb(dev, bin2bcd(tm->tm_hour), M48T86_HOUR);128 m48t86_writeb(dev, bin2bcd(tm->tm_mday), M48T86_DOM);129 m48t86_writeb(dev, bin2bcd(tm->tm_mon + 1), M48T86_MONTH);130 m48t86_writeb(dev, bin2bcd(tm->tm_year % 100), M48T86_YEAR);131 m48t86_writeb(dev, bin2bcd(tm->tm_wday), M48T86_DOW);132 }133 134 /* update ended */135 reg &= ~M48T86_B_SET;136 m48t86_writeb(dev, reg, M48T86_B);137 138 return 0;139}140 141static int m48t86_rtc_proc(struct device *dev, struct seq_file *seq)142{143 unsigned char reg;144 145 reg = m48t86_readb(dev, M48T86_B);146 147 seq_printf(seq, "mode\t\t: %s\n",148 (reg & M48T86_B_DM) ? "binary" : "bcd");149 150 reg = m48t86_readb(dev, M48T86_D);151 152 seq_printf(seq, "battery\t\t: %s\n",153 (reg & M48T86_D_VRT) ? "ok" : "exhausted");154 155 return 0;156}157 158static const struct rtc_class_ops m48t86_rtc_ops = {159 .read_time = m48t86_rtc_read_time,160 .set_time = m48t86_rtc_set_time,161 .proc = m48t86_rtc_proc,162};163 164static int m48t86_nvram_read(void *priv, unsigned int off, void *buf,165 size_t count)166{167 struct device *dev = priv;168 unsigned int i;169 170 for (i = 0; i < count; i++)171 ((u8 *)buf)[i] = m48t86_readb(dev, M48T86_NVRAM(off + i));172 173 return 0;174}175 176static int m48t86_nvram_write(void *priv, unsigned int off, void *buf,177 size_t count)178{179 struct device *dev = priv;180 unsigned int i;181 182 for (i = 0; i < count; i++)183 m48t86_writeb(dev, ((u8 *)buf)[i], M48T86_NVRAM(off + i));184 185 return 0;186}187 188/*189 * The RTC is an optional feature at purchase time on some Technologic Systems190 * boards. Verify that it actually exists by checking if the last two bytes191 * of the NVRAM can be changed.192 *193 * This is based on the method used in their rtc7800.c example.194 */195static bool m48t86_verify_chip(struct platform_device *pdev)196{197 unsigned int offset0 = M48T86_NVRAM(M48T86_NVRAM_LEN - 2);198 unsigned int offset1 = M48T86_NVRAM(M48T86_NVRAM_LEN - 1);199 unsigned char tmp0, tmp1;200 201 tmp0 = m48t86_readb(&pdev->dev, offset0);202 tmp1 = m48t86_readb(&pdev->dev, offset1);203 204 m48t86_writeb(&pdev->dev, 0x00, offset0);205 m48t86_writeb(&pdev->dev, 0x55, offset1);206 if (m48t86_readb(&pdev->dev, offset1) == 0x55) {207 m48t86_writeb(&pdev->dev, 0xaa, offset1);208 if (m48t86_readb(&pdev->dev, offset1) == 0xaa &&209 m48t86_readb(&pdev->dev, offset0) == 0x00) {210 m48t86_writeb(&pdev->dev, tmp0, offset0);211 m48t86_writeb(&pdev->dev, tmp1, offset1);212 213 return true;214 }215 }216 return false;217}218 219static int m48t86_rtc_probe(struct platform_device *pdev)220{221 struct m48t86_rtc_info *info;222 unsigned char reg;223 int err;224 struct nvmem_config m48t86_nvmem_cfg = {225 .name = "m48t86_nvram",226 .word_size = 1,227 .stride = 1,228 .size = M48T86_NVRAM_LEN,229 .reg_read = m48t86_nvram_read,230 .reg_write = m48t86_nvram_write,231 .priv = &pdev->dev,232 };233 234 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);235 if (!info)236 return -ENOMEM;237 238 info->index_reg = devm_platform_ioremap_resource(pdev, 0);239 if (IS_ERR(info->index_reg))240 return PTR_ERR(info->index_reg);241 242 info->data_reg = devm_platform_ioremap_resource(pdev, 1);243 if (IS_ERR(info->data_reg))244 return PTR_ERR(info->data_reg);245 246 dev_set_drvdata(&pdev->dev, info);247 248 if (!m48t86_verify_chip(pdev)) {249 dev_info(&pdev->dev, "RTC not present\n");250 return -ENODEV;251 }252 253 info->rtc = devm_rtc_allocate_device(&pdev->dev);254 if (IS_ERR(info->rtc))255 return PTR_ERR(info->rtc);256 257 info->rtc->ops = &m48t86_rtc_ops;258 259 err = devm_rtc_register_device(info->rtc);260 if (err)261 return err;262 263 devm_rtc_nvmem_register(info->rtc, &m48t86_nvmem_cfg);264 265 /* read battery status */266 reg = m48t86_readb(&pdev->dev, M48T86_D);267 dev_info(&pdev->dev, "battery %s\n",268 (reg & M48T86_D_VRT) ? "ok" : "exhausted");269 270 return 0;271}272 273static const struct of_device_id m48t86_rtc_of_ids[] = {274 { .compatible = "st,m48t86" },275 { /* sentinel */ }276};277MODULE_DEVICE_TABLE(of, m48t86_rtc_of_ids);278 279static struct platform_driver m48t86_rtc_platform_driver = {280 .driver = {281 .name = "rtc-m48t86",282 .of_match_table = m48t86_rtc_of_ids,283 },284 .probe = m48t86_rtc_probe,285};286 287module_platform_driver(m48t86_rtc_platform_driver);288 289MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");290MODULE_DESCRIPTION("M48T86 RTC driver");291MODULE_LICENSE("GPL");292MODULE_ALIAS("platform:rtc-m48t86");293