194 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Driver for the SGS-Thomson M48T35 Timekeeper RAM chip4 *5 * Copyright (C) 2000 Silicon Graphics, Inc.6 * Written by Ulf Carlsson (ulfc@engr.sgi.com)7 *8 * Copyright (C) 2008 Thomas Bogendoerfer9 *10 * Based on code written by Paul Gortmaker.11 */12 13#include <linux/module.h>14#include <linux/rtc.h>15#include <linux/slab.h>16#include <linux/platform_device.h>17#include <linux/bcd.h>18#include <linux/io.h>19#include <linux/err.h>20 21struct m48t35_rtc {22 u8 pad[0x7ff8]; /* starts at 0x7ff8 */23#ifdef CONFIG_SGI_IP2724 u8 hour;25 u8 min;26 u8 sec;27 u8 control;28 u8 year;29 u8 month;30 u8 date;31 u8 day;32#else33 u8 control;34 u8 sec;35 u8 min;36 u8 hour;37 u8 day;38 u8 date;39 u8 month;40 u8 year;41#endif42};43 44#define M48T35_RTC_SET 0x8045#define M48T35_RTC_READ 0x4046 47struct m48t35_priv {48 struct rtc_device *rtc;49 struct m48t35_rtc __iomem *reg;50 size_t size;51 unsigned long baseaddr;52 spinlock_t lock;53};54 55static int m48t35_read_time(struct device *dev, struct rtc_time *tm)56{57 struct m48t35_priv *priv = dev_get_drvdata(dev);58 u8 control;59 60 /*61 * Only the values that we read from the RTC are set. We leave62 * tm_wday, tm_yday and tm_isdst untouched. Even though the63 * RTC has RTC_DAY_OF_WEEK, we ignore it, as it is only updated64 * by the RTC when initially set to a non-zero value.65 */66 spin_lock_irq(&priv->lock);67 control = readb(&priv->reg->control);68 writeb(control | M48T35_RTC_READ, &priv->reg->control);69 tm->tm_sec = readb(&priv->reg->sec);70 tm->tm_min = readb(&priv->reg->min);71 tm->tm_hour = readb(&priv->reg->hour);72 tm->tm_mday = readb(&priv->reg->date);73 tm->tm_mon = readb(&priv->reg->month);74 tm->tm_year = readb(&priv->reg->year);75 writeb(control, &priv->reg->control);76 spin_unlock_irq(&priv->lock);77 78 tm->tm_sec = bcd2bin(tm->tm_sec);79 tm->tm_min = bcd2bin(tm->tm_min);80 tm->tm_hour = bcd2bin(tm->tm_hour);81 tm->tm_mday = bcd2bin(tm->tm_mday);82 tm->tm_mon = bcd2bin(tm->tm_mon);83 tm->tm_year = bcd2bin(tm->tm_year);84 85 /*86 * Account for differences between how the RTC uses the values87 * and how they are defined in a struct rtc_time;88 */89 tm->tm_year += 70;90 if (tm->tm_year <= 69)91 tm->tm_year += 100;92 93 tm->tm_mon--;94 return 0;95}96 97static int m48t35_set_time(struct device *dev, struct rtc_time *tm)98{99 struct m48t35_priv *priv = dev_get_drvdata(dev);100 unsigned char mon, day, hrs, min, sec;101 unsigned int yrs;102 u8 control;103 104 yrs = tm->tm_year + 1900;105 mon = tm->tm_mon + 1; /* tm_mon starts at zero */106 day = tm->tm_mday;107 hrs = tm->tm_hour;108 min = tm->tm_min;109 sec = tm->tm_sec;110 111 if (yrs < 1970)112 return -EINVAL;113 114 yrs -= 1970;115 if (yrs > 255) /* They are unsigned */116 return -EINVAL;117 118 if (yrs > 169)119 return -EINVAL;120 121 if (yrs >= 100)122 yrs -= 100;123 124 sec = bin2bcd(sec);125 min = bin2bcd(min);126 hrs = bin2bcd(hrs);127 day = bin2bcd(day);128 mon = bin2bcd(mon);129 yrs = bin2bcd(yrs);130 131 spin_lock_irq(&priv->lock);132 control = readb(&priv->reg->control);133 writeb(control | M48T35_RTC_SET, &priv->reg->control);134 writeb(yrs, &priv->reg->year);135 writeb(mon, &priv->reg->month);136 writeb(day, &priv->reg->date);137 writeb(hrs, &priv->reg->hour);138 writeb(min, &priv->reg->min);139 writeb(sec, &priv->reg->sec);140 writeb(control, &priv->reg->control);141 spin_unlock_irq(&priv->lock);142 return 0;143}144 145static const struct rtc_class_ops m48t35_ops = {146 .read_time = m48t35_read_time,147 .set_time = m48t35_set_time,148};149 150static int m48t35_probe(struct platform_device *pdev)151{152 struct resource *res;153 struct m48t35_priv *priv;154 155 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);156 if (!res)157 return -ENODEV;158 priv = devm_kzalloc(&pdev->dev, sizeof(struct m48t35_priv), GFP_KERNEL);159 if (!priv)160 return -ENOMEM;161 162 priv->size = resource_size(res);163 if (!devm_request_mem_region(&pdev->dev, res->start, priv->size,164 pdev->name))165 return -EBUSY;166 167 priv->baseaddr = res->start;168 priv->reg = devm_ioremap(&pdev->dev, priv->baseaddr, priv->size);169 if (!priv->reg)170 return -ENOMEM;171 172 spin_lock_init(&priv->lock);173 174 platform_set_drvdata(pdev, priv);175 176 priv->rtc = devm_rtc_device_register(&pdev->dev, "m48t35",177 &m48t35_ops, THIS_MODULE);178 return PTR_ERR_OR_ZERO(priv->rtc);179}180 181static struct platform_driver m48t35_platform_driver = {182 .driver = {183 .name = "rtc-m48t35",184 },185 .probe = m48t35_probe,186};187 188module_platform_driver(m48t35_platform_driver);189 190MODULE_AUTHOR("Thomas Bogendoerfer <tsbogend@alpha.franken.de>");191MODULE_DESCRIPTION("M48T35 RTC driver");192MODULE_LICENSE("GPL");193MODULE_ALIAS("platform:rtc-m48t35");194