File Index
Files and directories
Sign in
FILE

rtc-macsmc.c

File preview with syntax highlighting.

linux-6.19.14/drivers/rtc/rtc-macsmc.c C
  1// SPDX-License-Identifier: GPL-2.0-only OR MIT
  2/*
  3 * Apple SMC RTC driver
  4 * Copyright The Asahi Linux Contributors
  5 */
  6
  7#include <linux/bitops.h>
  8#include <linux/mfd/macsmc.h>
  9#include <linux/module.h>
 10#include <linux/nvmem-consumer.h>
 11#include <linux/of.h>
 12#include <linux/platform_device.h>
 13#include <linux/rtc.h>
 14#include <linux/slab.h>
 15
 16/* 48-bit RTC */
 17#define RTC_BYTES 6
 18#define RTC_BITS (8 * RTC_BYTES)
 19
 20/* 32768 Hz clock */
 21#define RTC_SEC_SHIFT 15
 22
 23struct macsmc_rtc {
 24	struct device *dev;
 25	struct apple_smc *smc;
 26	struct rtc_device *rtc_dev;
 27	struct nvmem_cell *rtc_offset;
 28};
 29
 30static int macsmc_rtc_get_time(struct device *dev, struct rtc_time *tm)
 31{
 32	struct macsmc_rtc *rtc = dev_get_drvdata(dev);
 33	u64 ctr = 0, off = 0;
 34	time64_t now;
 35	void *p_off;
 36	size_t len;
 37	int ret;
 38
 39	ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES);
 40	if (ret < 0)
 41		return ret;
 42	if (ret != RTC_BYTES)
 43		return -EIO;
 44
 45	p_off = nvmem_cell_read(rtc->rtc_offset, &len);
 46	if (IS_ERR(p_off))
 47		return PTR_ERR(p_off);
 48	if (len < RTC_BYTES) {
 49		kfree(p_off);
 50		return -EIO;
 51	}
 52
 53	memcpy(&off, p_off, RTC_BYTES);
 54	kfree(p_off);
 55
 56	/* Sign extend from 48 to 64 bits, then arithmetic shift right 15 bits to get seconds */
 57	now = sign_extend64(ctr + off, RTC_BITS - 1) >> RTC_SEC_SHIFT;
 58	rtc_time64_to_tm(now, tm);
 59
 60	return ret;
 61}
 62
 63static int macsmc_rtc_set_time(struct device *dev, struct rtc_time *tm)
 64{
 65	struct macsmc_rtc *rtc = dev_get_drvdata(dev);
 66	u64 ctr = 0, off = 0;
 67	int ret;
 68
 69	ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES);
 70	if (ret < 0)
 71		return ret;
 72	if (ret != RTC_BYTES)
 73		return -EIO;
 74
 75	/* This sets the offset such that the set second begins now */
 76	off = (rtc_tm_to_time64(tm) << RTC_SEC_SHIFT) - ctr;
 77	return nvmem_cell_write(rtc->rtc_offset, &off, RTC_BYTES);
 78}
 79
 80static const struct rtc_class_ops macsmc_rtc_ops = {
 81	.read_time = macsmc_rtc_get_time,
 82	.set_time = macsmc_rtc_set_time,
 83};
 84
 85static int macsmc_rtc_probe(struct platform_device *pdev)
 86{
 87	struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
 88	struct macsmc_rtc *rtc;
 89
 90	/*
 91	 * MFD will probe this device even without a node in the device tree,
 92	 * thus bail out early if the SMC on the current machines does not
 93	 * support RTC and has no node in the device tree.
 94	 */
 95	if (!pdev->dev.of_node)
 96		return -ENODEV;
 97
 98	rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
 99	if (!rtc)
100		return -ENOMEM;
101
102	rtc->dev = &pdev->dev;
103	rtc->smc = smc;
104
105	rtc->rtc_offset = devm_nvmem_cell_get(&pdev->dev, "rtc_offset");
106	if (IS_ERR(rtc->rtc_offset))
107		return dev_err_probe(&pdev->dev, PTR_ERR(rtc->rtc_offset),
108				     "Failed to get rtc_offset NVMEM cell\n");
109
110	rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev);
111	if (IS_ERR(rtc->rtc_dev))
112		return PTR_ERR(rtc->rtc_dev);
113
114	rtc->rtc_dev->ops = &macsmc_rtc_ops;
115	rtc->rtc_dev->range_min = S64_MIN >> (RTC_SEC_SHIFT + (64 - RTC_BITS));
116	rtc->rtc_dev->range_max = S64_MAX >> (RTC_SEC_SHIFT + (64 - RTC_BITS));
117
118	platform_set_drvdata(pdev, rtc);
119
120	return devm_rtc_register_device(rtc->rtc_dev);
121}
122
123static const struct of_device_id macsmc_rtc_of_table[] = {
124	{ .compatible = "apple,smc-rtc", },
125	{}
126};
127MODULE_DEVICE_TABLE(of, macsmc_rtc_of_table);
128
129static struct platform_driver macsmc_rtc_driver = {
130	.driver = {
131		.name = "macsmc-rtc",
132		.of_match_table = macsmc_rtc_of_table,
133	},
134	.probe = macsmc_rtc_probe,
135};
136module_platform_driver(macsmc_rtc_driver);
137
138MODULE_LICENSE("Dual MIT/GPL");
139MODULE_DESCRIPTION("Apple SMC RTC driver");
140MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
~/ Read-only file preview.