forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
405 lines
10 KiB
405 lines
10 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* RTC driver for the interal RTC block in the Amlogic Meson6, Meson8, |
|
* Meson8b and Meson8m2 SoCs. |
|
* |
|
* The RTC is split in to two parts, the AHB front end and a simple serial |
|
* connection to the actual registers. This driver manages both parts. |
|
* |
|
* Copyright (c) 2018 Martin Blumenstingl <[email protected]> |
|
* Copyright (c) 2015 Ben Dooks <[email protected]> for Codethink Ltd |
|
* Based on origin by Carlo Caione <[email protected]> |
|
*/ |
|
|
|
#include <linux/bitfield.h> |
|
#include <linux/delay.h> |
|
#include <linux/io.h> |
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/nvmem-provider.h> |
|
#include <linux/of.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/regmap.h> |
|
#include <linux/regulator/consumer.h> |
|
#include <linux/reset.h> |
|
#include <linux/rtc.h> |
|
|
|
/* registers accessed from cpu bus */ |
|
#define RTC_ADDR0 0x00 |
|
#define RTC_ADDR0_LINE_SCLK BIT(0) |
|
#define RTC_ADDR0_LINE_SEN BIT(1) |
|
#define RTC_ADDR0_LINE_SDI BIT(2) |
|
#define RTC_ADDR0_START_SER BIT(17) |
|
#define RTC_ADDR0_WAIT_SER BIT(22) |
|
#define RTC_ADDR0_DATA GENMASK(31, 24) |
|
|
|
#define RTC_ADDR1 0x04 |
|
#define RTC_ADDR1_SDO BIT(0) |
|
#define RTC_ADDR1_S_READY BIT(1) |
|
|
|
#define RTC_ADDR2 0x08 |
|
#define RTC_ADDR3 0x0c |
|
|
|
#define RTC_REG4 0x10 |
|
#define RTC_REG4_STATIC_VALUE GENMASK(7, 0) |
|
|
|
/* rtc registers accessed via rtc-serial interface */ |
|
#define RTC_COUNTER (0) |
|
#define RTC_SEC_ADJ (2) |
|
#define RTC_REGMEM_0 (4) |
|
#define RTC_REGMEM_1 (5) |
|
#define RTC_REGMEM_2 (6) |
|
#define RTC_REGMEM_3 (7) |
|
|
|
#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 regulator *vdd; /* voltage input */ |
|
struct regmap *peripheral; /* peripheral registers */ |
|
struct regmap *serial; /* serial registers */ |
|
}; |
|
|
|
static const struct regmap_config meson_rtc_peripheral_regmap_config = { |
|
.name = "peripheral-registers", |
|
.reg_bits = 8, |
|
.val_bits = 32, |
|
.reg_stride = 4, |
|
.max_register = RTC_REG4, |
|
.fast_io = true, |
|
}; |
|
|
|
/* RTC front-end serialiser controls */ |
|
|
|
static void meson_rtc_sclk_pulse(struct meson_rtc *rtc) |
|
{ |
|
udelay(5); |
|
regmap_update_bits(rtc->peripheral, RTC_ADDR0, RTC_ADDR0_LINE_SCLK, 0); |
|
udelay(5); |
|
regmap_update_bits(rtc->peripheral, RTC_ADDR0, RTC_ADDR0_LINE_SCLK, |
|
RTC_ADDR0_LINE_SCLK); |
|
} |
|
|
|
static void meson_rtc_send_bit(struct meson_rtc *rtc, unsigned int bit) |
|
{ |
|
regmap_update_bits(rtc->peripheral, RTC_ADDR0, RTC_ADDR0_LINE_SDI, |
|
bit ? RTC_ADDR0_LINE_SDI : 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) |
|
{ |
|
regmap_update_bits(rtc->peripheral, RTC_ADDR0, RTC_ADDR0_LINE_SEN, 0); |
|
regmap_update_bits(rtc->peripheral, RTC_ADDR0, RTC_ADDR0_LINE_SDI, 0); |
|
meson_rtc_send_bit(rtc, mode); |
|
regmap_update_bits(rtc->peripheral, RTC_ADDR0, RTC_ADDR0_LINE_SDI, 0); |
|
} |
|
|
|
static u32 meson_rtc_get_data(struct meson_rtc *rtc) |
|
{ |
|
u32 tmp, val = 0; |
|
int bit; |
|
|
|
for (bit = 0; bit < RTC_DATA_BITS; bit++) { |
|
meson_rtc_sclk_pulse(rtc); |
|
val <<= 1; |
|
|
|
regmap_read(rtc->peripheral, RTC_ADDR1, &tmp); |
|
val |= tmp & RTC_ADDR1_SDO; |
|
} |
|
|
|
return val; |
|
} |
|
|
|
static int meson_rtc_get_bus(struct meson_rtc *rtc) |
|
{ |
|
int ret, retries; |
|
u32 val; |
|
|
|
/* prepare bus for transfers, set all lines low */ |
|
val = RTC_ADDR0_LINE_SDI | RTC_ADDR0_LINE_SEN | RTC_ADDR0_LINE_SCLK; |
|
regmap_update_bits(rtc->peripheral, RTC_ADDR0, val, 0); |
|
|
|
for (retries = 0; retries < 3; retries++) { |
|
/* wait for the bus to be ready */ |
|
if (!regmap_read_poll_timeout(rtc->peripheral, RTC_ADDR1, val, |
|
val & RTC_ADDR1_S_READY, 10, |
|
10000)) |
|
return 0; |
|
|
|
dev_warn(rtc->dev, "failed to get bus, resetting RTC\n"); |
|
|
|
ret = reset_control_reset(rtc->reset); |
|
if (ret) |
|
return ret; |
|
} |
|
|
|
dev_err(rtc->dev, "bus is not ready\n"); |
|
return -ETIMEDOUT; |
|
} |
|
|
|
static int meson_rtc_serial_bus_reg_read(void *context, unsigned int reg, |
|
unsigned int *data) |
|
{ |
|
struct meson_rtc *rtc = context; |
|
int ret; |
|
|
|
ret = meson_rtc_get_bus(rtc); |
|
if (ret) |
|
return ret; |
|
|
|
regmap_update_bits(rtc->peripheral, RTC_ADDR0, RTC_ADDR0_LINE_SEN, |
|
RTC_ADDR0_LINE_SEN); |
|
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_serial_bus_reg_write(void *context, unsigned int reg, |
|
unsigned int data) |
|
{ |
|
struct meson_rtc *rtc = context; |
|
int ret; |
|
|
|
ret = meson_rtc_get_bus(rtc); |
|
if (ret) |
|
return ret; |
|
|
|
regmap_update_bits(rtc->peripheral, RTC_ADDR0, RTC_ADDR0_LINE_SEN, |
|
RTC_ADDR0_LINE_SEN); |
|
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 const struct regmap_bus meson_rtc_serial_bus = { |
|
.reg_read = meson_rtc_serial_bus_reg_read, |
|
.reg_write = meson_rtc_serial_bus_reg_write, |
|
}; |
|
|
|
static const struct regmap_config meson_rtc_serial_regmap_config = { |
|
.name = "serial-registers", |
|
.reg_bits = 4, |
|
.reg_stride = 1, |
|
.val_bits = 32, |
|
.max_register = RTC_REGMEM_3, |
|
.fast_io = false, |
|
}; |
|
|
|
static int meson_rtc_write_static(struct meson_rtc *rtc, u32 data) |
|
{ |
|
u32 tmp; |
|
|
|
regmap_write(rtc->peripheral, RTC_REG4, |
|
FIELD_PREP(RTC_REG4_STATIC_VALUE, (data >> 8))); |
|
|
|
/* write the static value and start the auto serializer */ |
|
tmp = FIELD_PREP(RTC_ADDR0_DATA, (data & 0xff)) | RTC_ADDR0_START_SER; |
|
regmap_update_bits(rtc->peripheral, RTC_ADDR0, |
|
RTC_ADDR0_DATA | RTC_ADDR0_START_SER, tmp); |
|
|
|
/* wait for the auto serializer to complete */ |
|
return regmap_read_poll_timeout(rtc->peripheral, RTC_REG4, tmp, |
|
!(tmp & RTC_ADDR0_WAIT_SER), 10, |
|
10000); |
|
} |
|
|
|
/* RTC interface layer functions */ |
|
|
|
static int meson_rtc_gettime(struct device *dev, struct rtc_time *tm) |
|
{ |
|
struct meson_rtc *rtc = dev_get_drvdata(dev); |
|
u32 time; |
|
int ret; |
|
|
|
ret = regmap_read(rtc->serial, RTC_COUNTER, &time); |
|
if (!ret) |
|
rtc_time64_to_tm(time, tm); |
|
|
|
return ret; |
|
} |
|
|
|
static int meson_rtc_settime(struct device *dev, struct rtc_time *tm) |
|
{ |
|
struct meson_rtc *rtc = dev_get_drvdata(dev); |
|
|
|
return regmap_write(rtc->serial, RTC_COUNTER, rtc_tm_to_time64(tm)); |
|
} |
|
|
|
static const struct rtc_class_ops meson_rtc_ops = { |
|
.read_time = meson_rtc_gettime, |
|
.set_time = meson_rtc_settime, |
|
}; |
|
|
|
/* NVMEM interface layer functions */ |
|
|
|
static int meson_rtc_regmem_read(void *context, unsigned int offset, |
|
void *buf, size_t bytes) |
|
{ |
|
struct meson_rtc *rtc = context; |
|
unsigned int read_offset, read_size; |
|
|
|
read_offset = RTC_REGMEM_0 + (offset / 4); |
|
read_size = bytes / 4; |
|
|
|
return regmap_bulk_read(rtc->serial, read_offset, buf, read_size); |
|
} |
|
|
|
static int meson_rtc_regmem_write(void *context, unsigned int offset, |
|
void *buf, size_t bytes) |
|
{ |
|
struct meson_rtc *rtc = context; |
|
unsigned int write_offset, write_size; |
|
|
|
write_offset = RTC_REGMEM_0 + (offset / 4); |
|
write_size = bytes / 4; |
|
|
|
return regmap_bulk_write(rtc->serial, write_offset, buf, write_size); |
|
} |
|
|
|
static int meson_rtc_probe(struct platform_device *pdev) |
|
{ |
|
struct nvmem_config meson_rtc_nvmem_config = { |
|
.name = "meson-rtc-regmem", |
|
.type = NVMEM_TYPE_BATTERY_BACKED, |
|
.word_size = 4, |
|
.stride = 4, |
|
.size = 4 * 4, |
|
.reg_read = meson_rtc_regmem_read, |
|
.reg_write = meson_rtc_regmem_write, |
|
}; |
|
struct device *dev = &pdev->dev; |
|
struct meson_rtc *rtc; |
|
void __iomem *base; |
|
int ret; |
|
u32 tm; |
|
|
|
rtc = devm_kzalloc(dev, sizeof(struct meson_rtc), GFP_KERNEL); |
|
if (!rtc) |
|
return -ENOMEM; |
|
|
|
rtc->rtc = devm_rtc_allocate_device(dev); |
|
if (IS_ERR(rtc->rtc)) |
|
return PTR_ERR(rtc->rtc); |
|
|
|
platform_set_drvdata(pdev, rtc); |
|
|
|
rtc->dev = dev; |
|
|
|
rtc->rtc->ops = &meson_rtc_ops; |
|
rtc->rtc->range_max = U32_MAX; |
|
|
|
base = devm_platform_ioremap_resource(pdev, 0); |
|
if (IS_ERR(base)) |
|
return PTR_ERR(base); |
|
|
|
rtc->peripheral = devm_regmap_init_mmio(dev, base, |
|
&meson_rtc_peripheral_regmap_config); |
|
if (IS_ERR(rtc->peripheral)) { |
|
dev_err(dev, "failed to create peripheral regmap\n"); |
|
return PTR_ERR(rtc->peripheral); |
|
} |
|
|
|
rtc->reset = devm_reset_control_get(dev, NULL); |
|
if (IS_ERR(rtc->reset)) { |
|
dev_err(dev, "missing reset line\n"); |
|
return PTR_ERR(rtc->reset); |
|
} |
|
|
|
rtc->vdd = devm_regulator_get(dev, "vdd"); |
|
if (IS_ERR(rtc->vdd)) { |
|
dev_err(dev, "failed to get the vdd-supply\n"); |
|
return PTR_ERR(rtc->vdd); |
|
} |
|
|
|
ret = regulator_enable(rtc->vdd); |
|
if (ret) { |
|
dev_err(dev, "failed to enable vdd-supply\n"); |
|
return ret; |
|
} |
|
|
|
ret = meson_rtc_write_static(rtc, MESON_STATIC_DEFAULT); |
|
if (ret) { |
|
dev_err(dev, "failed to set static values\n"); |
|
goto out_disable_vdd; |
|
} |
|
|
|
rtc->serial = devm_regmap_init(dev, &meson_rtc_serial_bus, rtc, |
|
&meson_rtc_serial_regmap_config); |
|
if (IS_ERR(rtc->serial)) { |
|
dev_err(dev, "failed to create serial regmap\n"); |
|
ret = PTR_ERR(rtc->serial); |
|
goto out_disable_vdd; |
|
} |
|
|
|
/* |
|
* 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. |
|
*/ |
|
ret = regmap_read(rtc->serial, RTC_COUNTER, &tm); |
|
if (ret) { |
|
dev_err(dev, "cannot read RTC counter, RTC not functional\n"); |
|
goto out_disable_vdd; |
|
} |
|
|
|
meson_rtc_nvmem_config.priv = rtc; |
|
ret = devm_rtc_nvmem_register(rtc->rtc, &meson_rtc_nvmem_config); |
|
if (ret) |
|
goto out_disable_vdd; |
|
|
|
ret = devm_rtc_register_device(rtc->rtc); |
|
if (ret) |
|
goto out_disable_vdd; |
|
|
|
return 0; |
|
|
|
out_disable_vdd: |
|
regulator_disable(rtc->vdd); |
|
return ret; |
|
} |
|
|
|
static const __maybe_unused struct of_device_id meson_rtc_dt_match[] = { |
|
{ .compatible = "amlogic,meson6-rtc", }, |
|
{ .compatible = "amlogic,meson8-rtc", }, |
|
{ .compatible = "amlogic,meson8b-rtc", }, |
|
{ .compatible = "amlogic,meson8m2-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 <[email protected]>"); |
|
MODULE_AUTHOR("Martin Blumenstingl <[email protected]>"); |
|
MODULE_LICENSE("GPL v2"); |
|
MODULE_ALIAS("platform:meson-rtc");
|
|
|