18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* rtc-bq4802.c: TI BQ4802 RTC driver. 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (C) 2008 David S. Miller <davem@davemloft.net> 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/kernel.h> 88c2ecf20Sopenharmony_ci#include <linux/module.h> 98c2ecf20Sopenharmony_ci#include <linux/init.h> 108c2ecf20Sopenharmony_ci#include <linux/io.h> 118c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 128c2ecf20Sopenharmony_ci#include <linux/rtc.h> 138c2ecf20Sopenharmony_ci#include <linux/bcd.h> 148c2ecf20Sopenharmony_ci#include <linux/slab.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ciMODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); 178c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TI BQ4802 RTC driver"); 188c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistruct bq4802 { 218c2ecf20Sopenharmony_ci void __iomem *regs; 228c2ecf20Sopenharmony_ci unsigned long ioport; 238c2ecf20Sopenharmony_ci struct rtc_device *rtc; 248c2ecf20Sopenharmony_ci spinlock_t lock; 258c2ecf20Sopenharmony_ci struct resource *r; 268c2ecf20Sopenharmony_ci u8 (*read)(struct bq4802 *, int); 278c2ecf20Sopenharmony_ci void (*write)(struct bq4802 *, int, u8); 288c2ecf20Sopenharmony_ci}; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistatic u8 bq4802_read_io(struct bq4802 *p, int off) 318c2ecf20Sopenharmony_ci{ 328c2ecf20Sopenharmony_ci return inb(p->ioport + off); 338c2ecf20Sopenharmony_ci} 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_cistatic void bq4802_write_io(struct bq4802 *p, int off, u8 val) 368c2ecf20Sopenharmony_ci{ 378c2ecf20Sopenharmony_ci outb(val, p->ioport + off); 388c2ecf20Sopenharmony_ci} 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic u8 bq4802_read_mem(struct bq4802 *p, int off) 418c2ecf20Sopenharmony_ci{ 428c2ecf20Sopenharmony_ci return readb(p->regs + off); 438c2ecf20Sopenharmony_ci} 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic void bq4802_write_mem(struct bq4802 *p, int off, u8 val) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci writeb(val, p->regs + off); 488c2ecf20Sopenharmony_ci} 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistatic int bq4802_read_time(struct device *dev, struct rtc_time *tm) 518c2ecf20Sopenharmony_ci{ 528c2ecf20Sopenharmony_ci struct bq4802 *p = dev_get_drvdata(dev); 538c2ecf20Sopenharmony_ci unsigned long flags; 548c2ecf20Sopenharmony_ci unsigned int century; 558c2ecf20Sopenharmony_ci u8 val; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci spin_lock_irqsave(&p->lock, flags); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci val = p->read(p, 0x0e); 608c2ecf20Sopenharmony_ci p->write(p, 0xe, val | 0x08); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci tm->tm_sec = p->read(p, 0x00); 638c2ecf20Sopenharmony_ci tm->tm_min = p->read(p, 0x02); 648c2ecf20Sopenharmony_ci tm->tm_hour = p->read(p, 0x04); 658c2ecf20Sopenharmony_ci tm->tm_mday = p->read(p, 0x06); 668c2ecf20Sopenharmony_ci tm->tm_mon = p->read(p, 0x09); 678c2ecf20Sopenharmony_ci tm->tm_year = p->read(p, 0x0a); 688c2ecf20Sopenharmony_ci tm->tm_wday = p->read(p, 0x08); 698c2ecf20Sopenharmony_ci century = p->read(p, 0x0f); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci p->write(p, 0x0e, val); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&p->lock, flags); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci tm->tm_sec = bcd2bin(tm->tm_sec); 768c2ecf20Sopenharmony_ci tm->tm_min = bcd2bin(tm->tm_min); 778c2ecf20Sopenharmony_ci tm->tm_hour = bcd2bin(tm->tm_hour); 788c2ecf20Sopenharmony_ci tm->tm_mday = bcd2bin(tm->tm_mday); 798c2ecf20Sopenharmony_ci tm->tm_mon = bcd2bin(tm->tm_mon); 808c2ecf20Sopenharmony_ci tm->tm_year = bcd2bin(tm->tm_year); 818c2ecf20Sopenharmony_ci tm->tm_wday = bcd2bin(tm->tm_wday); 828c2ecf20Sopenharmony_ci century = bcd2bin(century); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci tm->tm_year += (century * 100); 858c2ecf20Sopenharmony_ci tm->tm_year -= 1900; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci tm->tm_mon--; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci return 0; 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic int bq4802_set_time(struct device *dev, struct rtc_time *tm) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci struct bq4802 *p = dev_get_drvdata(dev); 958c2ecf20Sopenharmony_ci u8 sec, min, hrs, day, mon, yrs, century, val; 968c2ecf20Sopenharmony_ci unsigned long flags; 978c2ecf20Sopenharmony_ci unsigned int year; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci year = tm->tm_year + 1900; 1008c2ecf20Sopenharmony_ci century = year / 100; 1018c2ecf20Sopenharmony_ci yrs = year % 100; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci mon = tm->tm_mon + 1; /* tm_mon starts at zero */ 1048c2ecf20Sopenharmony_ci day = tm->tm_mday; 1058c2ecf20Sopenharmony_ci hrs = tm->tm_hour; 1068c2ecf20Sopenharmony_ci min = tm->tm_min; 1078c2ecf20Sopenharmony_ci sec = tm->tm_sec; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci sec = bin2bcd(sec); 1108c2ecf20Sopenharmony_ci min = bin2bcd(min); 1118c2ecf20Sopenharmony_ci hrs = bin2bcd(hrs); 1128c2ecf20Sopenharmony_ci day = bin2bcd(day); 1138c2ecf20Sopenharmony_ci mon = bin2bcd(mon); 1148c2ecf20Sopenharmony_ci yrs = bin2bcd(yrs); 1158c2ecf20Sopenharmony_ci century = bin2bcd(century); 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci spin_lock_irqsave(&p->lock, flags); 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci val = p->read(p, 0x0e); 1208c2ecf20Sopenharmony_ci p->write(p, 0x0e, val | 0x08); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci p->write(p, 0x00, sec); 1238c2ecf20Sopenharmony_ci p->write(p, 0x02, min); 1248c2ecf20Sopenharmony_ci p->write(p, 0x04, hrs); 1258c2ecf20Sopenharmony_ci p->write(p, 0x06, day); 1268c2ecf20Sopenharmony_ci p->write(p, 0x09, mon); 1278c2ecf20Sopenharmony_ci p->write(p, 0x0a, yrs); 1288c2ecf20Sopenharmony_ci p->write(p, 0x0f, century); 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci p->write(p, 0x0e, val); 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&p->lock, flags); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci return 0; 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic const struct rtc_class_ops bq4802_ops = { 1388c2ecf20Sopenharmony_ci .read_time = bq4802_read_time, 1398c2ecf20Sopenharmony_ci .set_time = bq4802_set_time, 1408c2ecf20Sopenharmony_ci}; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_cistatic int bq4802_probe(struct platform_device *pdev) 1438c2ecf20Sopenharmony_ci{ 1448c2ecf20Sopenharmony_ci struct bq4802 *p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); 1458c2ecf20Sopenharmony_ci int err = -ENOMEM; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci if (!p) 1488c2ecf20Sopenharmony_ci goto out; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci spin_lock_init(&p->lock); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci p->r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1538c2ecf20Sopenharmony_ci if (!p->r) { 1548c2ecf20Sopenharmony_ci p->r = platform_get_resource(pdev, IORESOURCE_IO, 0); 1558c2ecf20Sopenharmony_ci err = -EINVAL; 1568c2ecf20Sopenharmony_ci if (!p->r) 1578c2ecf20Sopenharmony_ci goto out; 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci if (p->r->flags & IORESOURCE_IO) { 1608c2ecf20Sopenharmony_ci p->ioport = p->r->start; 1618c2ecf20Sopenharmony_ci p->read = bq4802_read_io; 1628c2ecf20Sopenharmony_ci p->write = bq4802_write_io; 1638c2ecf20Sopenharmony_ci } else if (p->r->flags & IORESOURCE_MEM) { 1648c2ecf20Sopenharmony_ci p->regs = devm_ioremap(&pdev->dev, p->r->start, 1658c2ecf20Sopenharmony_ci resource_size(p->r)); 1668c2ecf20Sopenharmony_ci if (!p->regs){ 1678c2ecf20Sopenharmony_ci err = -ENOMEM; 1688c2ecf20Sopenharmony_ci goto out; 1698c2ecf20Sopenharmony_ci } 1708c2ecf20Sopenharmony_ci p->read = bq4802_read_mem; 1718c2ecf20Sopenharmony_ci p->write = bq4802_write_mem; 1728c2ecf20Sopenharmony_ci } else { 1738c2ecf20Sopenharmony_ci err = -EINVAL; 1748c2ecf20Sopenharmony_ci goto out; 1758c2ecf20Sopenharmony_ci } 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, p); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci p->rtc = devm_rtc_device_register(&pdev->dev, "bq4802", 1808c2ecf20Sopenharmony_ci &bq4802_ops, THIS_MODULE); 1818c2ecf20Sopenharmony_ci if (IS_ERR(p->rtc)) { 1828c2ecf20Sopenharmony_ci err = PTR_ERR(p->rtc); 1838c2ecf20Sopenharmony_ci goto out; 1848c2ecf20Sopenharmony_ci } 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci err = 0; 1878c2ecf20Sopenharmony_ciout: 1888c2ecf20Sopenharmony_ci return err; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci} 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci/* work with hotplug and coldplug */ 1938c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:rtc-bq4802"); 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic struct platform_driver bq4802_driver = { 1968c2ecf20Sopenharmony_ci .driver = { 1978c2ecf20Sopenharmony_ci .name = "rtc-bq4802", 1988c2ecf20Sopenharmony_ci }, 1998c2ecf20Sopenharmony_ci .probe = bq4802_probe, 2008c2ecf20Sopenharmony_ci}; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cimodule_platform_driver(bq4802_driver); 203