mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-05 10:04:12 +08:00
257062d267
The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is (mostly) ignored and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new() which already returns void. Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Link: https://lore.kernel.org/r/20230304133028.2135435-41-u.kleine-koenig@pengutronix.de Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
279 lines
6.5 KiB
C
279 lines
6.5 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* APM X-Gene SoC Real Time Clock Driver
|
|
*
|
|
* Copyright (c) 2014, Applied Micro Circuits Corporation
|
|
* Author: Rameshwar Prasad Sahu <rsahu@apm.com>
|
|
* Loc Ho <lho@apm.com>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/init.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/rtc.h>
|
|
#include <linux/slab.h>
|
|
|
|
/* RTC CSR Registers */
|
|
#define RTC_CCVR 0x00
|
|
#define RTC_CMR 0x04
|
|
#define RTC_CLR 0x08
|
|
#define RTC_CCR 0x0C
|
|
#define RTC_CCR_IE BIT(0)
|
|
#define RTC_CCR_MASK BIT(1)
|
|
#define RTC_CCR_EN BIT(2)
|
|
#define RTC_CCR_WEN BIT(3)
|
|
#define RTC_STAT 0x10
|
|
#define RTC_STAT_BIT BIT(0)
|
|
#define RTC_RSTAT 0x14
|
|
#define RTC_EOI 0x18
|
|
#define RTC_VER 0x1C
|
|
|
|
struct xgene_rtc_dev {
|
|
struct rtc_device *rtc;
|
|
void __iomem *csr_base;
|
|
struct clk *clk;
|
|
unsigned int irq_wake;
|
|
unsigned int irq_enabled;
|
|
};
|
|
|
|
static int xgene_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
|
{
|
|
struct xgene_rtc_dev *pdata = dev_get_drvdata(dev);
|
|
|
|
rtc_time64_to_tm(readl(pdata->csr_base + RTC_CCVR), tm);
|
|
return 0;
|
|
}
|
|
|
|
static int xgene_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
|
{
|
|
struct xgene_rtc_dev *pdata = dev_get_drvdata(dev);
|
|
|
|
/*
|
|
* NOTE: After the following write, the RTC_CCVR is only reflected
|
|
* after the update cycle of 1 seconds.
|
|
*/
|
|
writel((u32)rtc_tm_to_time64(tm), pdata->csr_base + RTC_CLR);
|
|
readl(pdata->csr_base + RTC_CLR); /* Force a barrier */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xgene_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
|
{
|
|
struct xgene_rtc_dev *pdata = dev_get_drvdata(dev);
|
|
|
|
/* If possible, CMR should be read here */
|
|
rtc_time64_to_tm(0, &alrm->time);
|
|
alrm->enabled = readl(pdata->csr_base + RTC_CCR) & RTC_CCR_IE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xgene_rtc_alarm_irq_enable(struct device *dev, u32 enabled)
|
|
{
|
|
struct xgene_rtc_dev *pdata = dev_get_drvdata(dev);
|
|
u32 ccr;
|
|
|
|
ccr = readl(pdata->csr_base + RTC_CCR);
|
|
if (enabled) {
|
|
ccr &= ~RTC_CCR_MASK;
|
|
ccr |= RTC_CCR_IE;
|
|
} else {
|
|
ccr &= ~RTC_CCR_IE;
|
|
ccr |= RTC_CCR_MASK;
|
|
}
|
|
writel(ccr, pdata->csr_base + RTC_CCR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xgene_rtc_alarm_irq_enabled(struct device *dev)
|
|
{
|
|
struct xgene_rtc_dev *pdata = dev_get_drvdata(dev);
|
|
|
|
return readl(pdata->csr_base + RTC_CCR) & RTC_CCR_IE ? 1 : 0;
|
|
}
|
|
|
|
static int xgene_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
|
{
|
|
struct xgene_rtc_dev *pdata = dev_get_drvdata(dev);
|
|
|
|
writel((u32)rtc_tm_to_time64(&alrm->time), pdata->csr_base + RTC_CMR);
|
|
|
|
xgene_rtc_alarm_irq_enable(dev, alrm->enabled);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct rtc_class_ops xgene_rtc_ops = {
|
|
.read_time = xgene_rtc_read_time,
|
|
.set_time = xgene_rtc_set_time,
|
|
.read_alarm = xgene_rtc_read_alarm,
|
|
.set_alarm = xgene_rtc_set_alarm,
|
|
.alarm_irq_enable = xgene_rtc_alarm_irq_enable,
|
|
};
|
|
|
|
static irqreturn_t xgene_rtc_interrupt(int irq, void *id)
|
|
{
|
|
struct xgene_rtc_dev *pdata = id;
|
|
|
|
/* Check if interrupt asserted */
|
|
if (!(readl(pdata->csr_base + RTC_STAT) & RTC_STAT_BIT))
|
|
return IRQ_NONE;
|
|
|
|
/* Clear interrupt */
|
|
readl(pdata->csr_base + RTC_EOI);
|
|
|
|
rtc_update_irq(pdata->rtc, 1, RTC_IRQF | RTC_AF);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int xgene_rtc_probe(struct platform_device *pdev)
|
|
{
|
|
struct xgene_rtc_dev *pdata;
|
|
int ret;
|
|
int irq;
|
|
|
|
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
|
if (!pdata)
|
|
return -ENOMEM;
|
|
platform_set_drvdata(pdev, pdata);
|
|
|
|
pdata->csr_base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(pdata->csr_base))
|
|
return PTR_ERR(pdata->csr_base);
|
|
|
|
pdata->rtc = devm_rtc_allocate_device(&pdev->dev);
|
|
if (IS_ERR(pdata->rtc))
|
|
return PTR_ERR(pdata->rtc);
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0)
|
|
return irq;
|
|
ret = devm_request_irq(&pdev->dev, irq, xgene_rtc_interrupt, 0,
|
|
dev_name(&pdev->dev), pdata);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Could not request IRQ\n");
|
|
return ret;
|
|
}
|
|
|
|
pdata->clk = devm_clk_get(&pdev->dev, NULL);
|
|
if (IS_ERR(pdata->clk)) {
|
|
dev_err(&pdev->dev, "Couldn't get the clock for RTC\n");
|
|
return -ENODEV;
|
|
}
|
|
ret = clk_prepare_enable(pdata->clk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Turn on the clock and the crystal */
|
|
writel(RTC_CCR_EN, pdata->csr_base + RTC_CCR);
|
|
|
|
ret = device_init_wakeup(&pdev->dev, 1);
|
|
if (ret) {
|
|
clk_disable_unprepare(pdata->clk);
|
|
return ret;
|
|
}
|
|
|
|
pdata->rtc->ops = &xgene_rtc_ops;
|
|
pdata->rtc->range_max = U32_MAX;
|
|
|
|
ret = devm_rtc_register_device(pdata->rtc);
|
|
if (ret) {
|
|
clk_disable_unprepare(pdata->clk);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void xgene_rtc_remove(struct platform_device *pdev)
|
|
{
|
|
struct xgene_rtc_dev *pdata = platform_get_drvdata(pdev);
|
|
|
|
xgene_rtc_alarm_irq_enable(&pdev->dev, 0);
|
|
device_init_wakeup(&pdev->dev, 0);
|
|
clk_disable_unprepare(pdata->clk);
|
|
}
|
|
|
|
static int __maybe_unused xgene_rtc_suspend(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct xgene_rtc_dev *pdata = platform_get_drvdata(pdev);
|
|
int irq;
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
|
|
/*
|
|
* If this RTC alarm will be used for waking the system up,
|
|
* don't disable it of course. Else we just disable the alarm
|
|
* and await suspension.
|
|
*/
|
|
if (device_may_wakeup(&pdev->dev)) {
|
|
if (!enable_irq_wake(irq))
|
|
pdata->irq_wake = 1;
|
|
} else {
|
|
pdata->irq_enabled = xgene_rtc_alarm_irq_enabled(dev);
|
|
xgene_rtc_alarm_irq_enable(dev, 0);
|
|
clk_disable_unprepare(pdata->clk);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused xgene_rtc_resume(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct xgene_rtc_dev *pdata = platform_get_drvdata(pdev);
|
|
int irq;
|
|
int rc;
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
|
|
if (device_may_wakeup(&pdev->dev)) {
|
|
if (pdata->irq_wake) {
|
|
disable_irq_wake(irq);
|
|
pdata->irq_wake = 0;
|
|
}
|
|
} else {
|
|
rc = clk_prepare_enable(pdata->clk);
|
|
if (rc) {
|
|
dev_err(dev, "Unable to enable clock error %d\n", rc);
|
|
return rc;
|
|
}
|
|
xgene_rtc_alarm_irq_enable(dev, pdata->irq_enabled);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static SIMPLE_DEV_PM_OPS(xgene_rtc_pm_ops, xgene_rtc_suspend, xgene_rtc_resume);
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id xgene_rtc_of_match[] = {
|
|
{.compatible = "apm,xgene-rtc" },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, xgene_rtc_of_match);
|
|
#endif
|
|
|
|
static struct platform_driver xgene_rtc_driver = {
|
|
.probe = xgene_rtc_probe,
|
|
.remove_new = xgene_rtc_remove,
|
|
.driver = {
|
|
.name = "xgene-rtc",
|
|
.pm = &xgene_rtc_pm_ops,
|
|
.of_match_table = of_match_ptr(xgene_rtc_of_match),
|
|
},
|
|
};
|
|
|
|
module_platform_driver(xgene_rtc_driver);
|
|
|
|
MODULE_DESCRIPTION("APM X-Gene SoC RTC driver");
|
|
MODULE_AUTHOR("Rameshwar Sahu <rsahu@apm.com>");
|
|
MODULE_LICENSE("GPL");
|