From patchwork Mon Aug 22 22:14:57 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ben Dooks X-Patchwork-Id: 9295667 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 41E0A607F0 for ; Tue, 23 Aug 2016 13:48:07 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 30BD128790 for ; Tue, 23 Aug 2016 13:48:07 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 24C4A2884D; Tue, 23 Aug 2016 13:48:07 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.2 required=2.0 tests=BAYES_00, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 4B1F128790 for ; Tue, 23 Aug 2016 13:48:06 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.85_2 #1 (Red Hat Linux)) id 1bcC3b-0003UE-UJ; Tue, 23 Aug 2016 13:47:59 +0000 Received: from merlin.infradead.org ([2001:4978:20e::2]) by bombadil.infradead.org with esmtps (Exim 4.85_2 #1 (Red Hat Linux)) id 1bcC3a-0003Tj-5q for linux-amlogic@bombadil.infradead.org; Tue, 23 Aug 2016 13:47:58 +0000 Received: from [75.98.193.200] (helo=freya) by merlin.infradead.org with esmtps (Exim 4.85_2 #1 (Red Hat Linux)) id 1bcC3Y-0000Nx-N1 for linux-amlogic@lists.infradead.org; Tue, 23 Aug 2016 13:47:57 +0000 Received: from ben by freya with local (Exim 4.87) (envelope-from ) id 1bbxUk-0005jF-FD; Mon, 22 Aug 2016 23:15:02 +0100 From: Ben Dooks To: linux-amlogic@lists.infradead.org Subject: [PATCH 1/5] rtc: support for amlogic meson rtc Date: Mon, 22 Aug 2016 23:14:57 +0100 Message-Id: <20160822221501.21945-2-ben-linux@fluff.org> X-Mailer: git-send-email 2.9.3 In-Reply-To: <20160822221501.21945-1-ben-linux@fluff.org> References: <20160822221501.21945-1-ben-linux@fluff.org> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20160823_094756_807817_42798398 X-CRM114-Status: GOOD ( 28.41 ) X-BeenThere: linux-amlogic@lists.infradead.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Ben Dooks MIME-Version: 1.0 Sender: "linux-amlogic" Errors-To: linux-amlogic-bounces+patchwork-linux-amlogic=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP From: Ben Dooks Add support for the AMLogic meson8b/meson6 RTC block. Signed-off-by: Ben Dooks --- Changes v5: - fixed spacing error - fixed spelling errors - added MESON_STATIC_DEFAULT to reduce over 80-col lines - fixed use of unsigned by moving to unsigned int - remove unused meson_rtc_prep_bus() function - moved to using time64_t Changes since V3: <1467748122-14833-1-git-send-email-ben.dooks@codethink.co.uk> - Added Kevin's tested by Changes since V1: <1467039135-26588-1-git-send-email-ben.dooks@codethink.co.uk> - Fixed checkpatch and sparse errors - Fixed minor locking bug when writing static-control register - Updated the code ordering in the rtc-meson.c driver - Fixed alphanumeric ordering in on-cpu rtc block - Added Rob Hering's ack for the DT binding documentation - Added better description to the RTC driver header. --- drivers/rtc/Kconfig | 11 ++ drivers/rtc/Makefile | 1 + drivers/rtc/rtc-meson.c | 341 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 353 insertions(+) create mode 100644 drivers/rtc/rtc-meson.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index e215f50..40faca1 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1228,6 +1228,17 @@ config RTC_DRV_IMXDI This driver can also be built as a module, if so, the module will be called "rtc-imxdi". +config RTC_DRV_MESON + tristate "AMLogic Meson Real Time Clock" + depends on ARCH_MESON || COMPILE_TEST + help + Support for AMLogic Meson RTC block in many of the Meson SoC + devices. The driver supports basic read/write of the time. + + This driver can also be built as a module, if so, the module + will be called "rtc-meson". + + config RTC_DRV_OMAP tristate "TI OMAP Real Time Clock" depends on ARCH_OMAP || ARCH_DAVINCI || COMPILE_TEST diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 7cf7ad5..e1bf77c 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -94,6 +94,7 @@ obj-$(CONFIG_RTC_DRV_MAX8997) += rtc-max8997.o obj-$(CONFIG_RTC_DRV_MAX8998) += rtc-max8998.o obj-$(CONFIG_RTC_DRV_MC13XXX) += rtc-mc13xxx.o obj-$(CONFIG_RTC_DRV_MCP795) += rtc-mcp795.o +obj-$(CONFIG_RTC_DRV_MESON) += rtc-meson.o obj-$(CONFIG_RTC_DRV_MOXART) += rtc-moxart.o obj-$(CONFIG_RTC_DRV_MPC5121) += rtc-mpc5121.o obj-$(CONFIG_RTC_DRV_VRTC) += rtc-mrst.o diff --git a/drivers/rtc/rtc-meson.c b/drivers/rtc/rtc-meson.c new file mode 100644 index 0000000..07d8b8a --- /dev/null +++ b/drivers/rtc/rtc-meson.c @@ -0,0 +1,341 @@ +/* drivers/rtc/rtc-meson.c + * + * Copyright (c) 2015 Codethink Ltd + * Ben Dooks + * Based on origin by Carlo Caione + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include +#include +#include +#include +#include +#include + +/* registers accessed from cpu bus */ +#define RTC_REG(x) ((x) * 4) /* rtc registers 0-4 */ + +#define LINE_SDI (1 << 2) +#define LINE_SEN (1 << 1) +#define LINE_SCLK (1 << 0) + +#define RTCREG0_START_SER BIT(17) +#define RTCREG0_WAIT_SER BIT(22) +#define RTC_REG0_DATA(__data) ((__data) << 24) + +#define RTCREG1_READY BIT(1) + +/* rtc registers accessed via rtc-serial interface */ +#define RTC_COUNTER (0) +#define RTC_SEC_ADJ (2) + +#define RTC_ADDR_BITS (3) /* number of address bits to send */ +#define RTC_DATA_BITS (32) /* number of data bits to tx/rx */ + +#define MESON_STATIC_BIAS_CUR (0x5 << 1) +#define MESON_STATIC_VOLTAGE (0x3 << 11) +#define MESON_STATIC_DEFAULT (MESON_STATIC_BIAS_CUR | MESON_STATIC_VOLTAGE) + +struct meson_rtc { + struct rtc_device *rtc; /* rtc device we created */ + struct device *dev; /* device we bound from */ + struct reset_control *reset; /* reset source */ + struct mutex lock; /* rtc lock */ + void __iomem *regs; /* rtc register access */ +}; + +static void meson_rtc_setline(struct meson_rtc *rtc, + unsigned int bit, unsigned int to) +{ + u32 reg0; + + reg0 = readl(rtc->regs + RTC_REG(0)); + if (to) + reg0 |= bit; + else + reg0 &= ~bit; + writel(reg0, rtc->regs + RTC_REG(0)); +} + +static void meson_rtc_sclk_pulse(struct meson_rtc *rtc) +{ + udelay(5); + meson_rtc_setline(rtc, LINE_SCLK, 0); + udelay(5); + meson_rtc_setline(rtc, LINE_SCLK, 1); +} + +static void meson_rtc_send_bit(struct meson_rtc *rtc, unsigned int bit) +{ + meson_rtc_setline(rtc, LINE_SDI, bit ? 1 : 0); + meson_rtc_sclk_pulse(rtc); +} + +static void meson_rtc_send_bits(struct meson_rtc *rtc, u32 data, + unsigned int nr) +{ + u32 bit = 1 << (nr - 1); + + while (bit) { + meson_rtc_send_bit(rtc, data & bit); + bit >>= 1; + } +} + +static void meson_rtc_set_dir(struct meson_rtc *rtc, u32 mode) +{ + meson_rtc_setline(rtc, LINE_SEN, 0); + meson_rtc_setline(rtc, LINE_SDI, 0); + meson_rtc_send_bit(rtc, mode); + meson_rtc_setline(rtc, LINE_SDI, 0); +} + +static u32 meson_rtc_read_sdo(struct meson_rtc *rtc) +{ + return readl(rtc->regs + RTC_REG(1)) & BIT(0); +} + +static u32 meson_rtc_get_data(struct meson_rtc *rtc) +{ + u32 val = 0; + int bit; + + for (bit = 0; bit < RTC_DATA_BITS; bit++) { + meson_rtc_sclk_pulse(rtc); + val <<= 1; + val |= meson_rtc_read_sdo(rtc); + } + + return val; +} + +static int meson_rtc_wait_bus(struct meson_rtc *rtc, unsigned int timeout_ms) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms); + u32 val; + + while (time_before(jiffies, timeout)) { + val = readl(rtc->regs + RTC_REG(1)); + if (val & RTCREG1_READY) + return 1; + + dev_dbg(rtc->dev, "%s: reg0=%08x reg1=%08x\n", + __func__, readl(rtc->regs + RTC_REG(0)), val); + msleep(10); + } + + return 0; +} + +static int meson_rtc_get_bus(struct meson_rtc *rtc) +{ + int ret, retries = 3; + u32 val; + + val = readl(rtc->regs + RTC_REG(0)); + val &= ~(LINE_SDI | LINE_SEN | LINE_SCLK); + writel(val, rtc->regs + RTC_REG(0)); + + while (retries) { + if (meson_rtc_wait_bus(rtc, 300)) + return 0; + + dev_warn(rtc->dev, "failed to get bus, re-setting\n"); + + retries--; + ret = reset_control_reset(rtc->reset); + if (ret) + return ret; + } + + dev_err(rtc->dev, "%s: bus is not ready\n", __func__); + return -ETIMEDOUT; +} + +static int meson_rtc_read(struct meson_rtc *rtc, unsigned int reg, u32 *data) +{ + int ret; + + ret = meson_rtc_get_bus(rtc); + if (ret) + return ret; + + meson_rtc_setline(rtc, LINE_SEN, 1); + meson_rtc_send_bits(rtc, reg, RTC_ADDR_BITS); + meson_rtc_set_dir(rtc, 0); + *data = meson_rtc_get_data(rtc); + + return 0; +} + +static int meson_rtc_write(struct meson_rtc *rtc, unsigned int reg, u32 data) +{ + int ret; + + dev_dbg(rtc->dev, "%s: reg %d val %08x)\n", __func__, reg, data); + + ret = meson_rtc_get_bus(rtc); + if (ret) + return ret; + + meson_rtc_setline(rtc, LINE_SEN, 1); + meson_rtc_send_bits(rtc, data, RTC_DATA_BITS); + meson_rtc_send_bits(rtc, reg, RTC_ADDR_BITS); + meson_rtc_set_dir(rtc, 1); + + return 0; +} + +static int meson_rtc_gettime(struct device *dev, struct rtc_time *tm) +{ + struct meson_rtc *rtc = dev_get_drvdata(dev); + int ret; + u32 time; + + mutex_lock(&rtc->lock); + + ret = meson_rtc_read(rtc, RTC_COUNTER, &time); + if (!ret) { + rtc_time64_to_tm(time, tm); + dev_dbg(dev, "read time %u\n", time); + } + + mutex_unlock(&rtc->lock); + return ret; +} + +static int meson_rtc_settime(struct device *dev, struct rtc_time *tm) +{ + struct meson_rtc *rtc = dev_get_drvdata(dev); + time64_t time; + int ret; + + time = rtc_tm_to_time64(tm); + if (time >= ((time64_t)1 << 32)) { + dev_warn(dev, "%s: time is out of 32bit range\n", __func__); + return -ERANGE; + } + + mutex_lock(&rtc->lock); + ret = meson_rtc_write(rtc, RTC_COUNTER, time); + mutex_unlock(&rtc->lock); + return ret; +} + +static const struct rtc_class_ops meson_rtc_ops = { + .read_time = meson_rtc_gettime, + .set_time = meson_rtc_settime, +}; + +static int meson_rtc_wait_serialiser(struct meson_rtc *rtc) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + if (!(readl(rtc->regs + RTC_REG(0)) & RTCREG0_WAIT_SER)) + return 0; + msleep(10); + } + + return -ETIMEDOUT; +} + +static int meson_rtc_write_static(struct meson_rtc *rtc, u32 data) +{ + u32 tmp; + + mutex_lock(&rtc->lock); + + writel(data >> 8, rtc->regs + RTC_REG(4)); + + tmp = readl(rtc->regs + RTC_REG(0)); + tmp &= ~RTC_REG0_DATA(0xff); + tmp |= RTC_REG0_DATA(data); + tmp |= RTCREG0_START_SER; + + writel(tmp, rtc->regs + RTC_REG(0)); + + if (meson_rtc_wait_serialiser(rtc)) + return -ETIMEDOUT; + + dev_dbg(rtc->dev, "rtc_reg0 = %08x\n", readl(rtc->regs + RTC_REG(0))); + mutex_unlock(&rtc->lock); + + return 0; +} + +static int meson_rtc_probe(struct platform_device *pdev) +{ + struct meson_rtc *rtc; + struct device *dev = &pdev->dev; + struct resource *res; + u32 tm; + int ret; + + rtc = devm_kzalloc(dev, sizeof(struct meson_rtc), GFP_KERNEL); + if (!rtc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rtc->regs = devm_ioremap_resource(dev, res); + rtc->dev = dev; + + if (IS_ERR(rtc->regs)) + return PTR_ERR(rtc->regs); + + mutex_init(&rtc->lock); + platform_set_drvdata(pdev, rtc); + + ret = meson_rtc_write_static(rtc, MESON_STATIC_DEFAULT); + if (ret) { + dev_err(dev, "failed to set static values\n"); + return ret; + } + + rtc->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(rtc->reset)) + dev_warn(dev, "no reset available, rtc may not work\n"); + + /* check if we can read RTC counter, if not then the RTC is probably + * not functional. If it isn't probably best to not bind the device. + */ + ret = meson_rtc_read(rtc, RTC_COUNTER, &tm); + if (ret) { + dev_err(dev, "cannot read RTC counter, RTC not functional\n"); + return ret; + } + + rtc->rtc = devm_rtc_device_register(dev, "meson", + &meson_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc->rtc)) + return PTR_ERR(rtc->rtc); + + return 0; +} + +static const struct of_device_id meson_rtc_dt_match[] = { + { .compatible = "amlogic,meson6-rtc", }, + { .compatible = "amlogic,meson8-rtc", }, + { .compatible = "amlogic,meson8b-rtc", }, + { }, +}; +MODULE_DEVICE_TABLE(of, meson_rtc_dt_match); + +static struct platform_driver meson_rtc_driver = { + .probe = meson_rtc_probe, + .driver = { + .name = "meson-rtc", + .of_match_table = of_match_ptr(meson_rtc_dt_match), + }, +}; +module_platform_driver(meson_rtc_driver); + +MODULE_DESCRIPTION("AMLogic MESON RTC Driver"); +MODULE_AUTHOR("Ben Dooks "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:meson-rtc");