diff options
-rw-r--r-- | drivers/rtc/rtc-abx80x.c | 77 |
1 files changed, 77 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-abx80x.c b/drivers/rtc/rtc-abx80x.c index 2e0e6432901b..f34a2e59cac7 100644 --- a/drivers/rtc/rtc-abx80x.c +++ b/drivers/rtc/rtc-abx80x.c @@ -11,6 +11,7 @@ */ #include <linux/bcd.h> +#include <linux/bitfield.h> #include <linux/i2c.h> #include <linux/kstrtox.h> #include <linux/module.h> @@ -88,6 +89,16 @@ #define ABX8XX_TRICKLE_STANDARD_DIODE 0x8 #define ABX8XX_TRICKLE_SCHOTTKY_DIODE 0x4 +#define ABX8XX_REG_EXTRAM 0x3f +#define ABX8XX_EXTRAM_XADS GENMASK(1, 0) + +#define ABX8XX_SRAM_BASE 0x40 +#define ABX8XX_SRAM_WIN_SIZE 0x40 +#define ABX8XX_RAM_SIZE 256 + +#define NVMEM_ADDR_LOWER GENMASK(5, 0) +#define NVMEM_ADDR_UPPER GENMASK(7, 6) + static u8 trickle_resistors[] = {0, 3, 6, 11}; enum abx80x_chip {AB0801, AB0803, AB0804, AB0805, @@ -674,6 +685,68 @@ static int abx80x_setup_watchdog(struct abx80x_priv *priv) } #endif +static int abx80x_nvmem_xfer(struct abx80x_priv *priv, unsigned int offset, + void *val, size_t bytes, bool write) +{ + int ret; + + while (bytes) { + u8 extram, reg, len, lower, upper; + + lower = FIELD_GET(NVMEM_ADDR_LOWER, offset); + upper = FIELD_GET(NVMEM_ADDR_UPPER, offset); + extram = FIELD_PREP(ABX8XX_EXTRAM_XADS, upper); + reg = ABX8XX_SRAM_BASE + lower; + len = min(lower + bytes, (size_t)ABX8XX_SRAM_WIN_SIZE) - lower; + len = min_t(u8, len, I2C_SMBUS_BLOCK_MAX); + + ret = i2c_smbus_write_byte_data(priv->client, ABX8XX_REG_EXTRAM, + extram); + if (ret) + return ret; + + if (write) + ret = i2c_smbus_write_i2c_block_data(priv->client, reg, + len, val); + else + ret = i2c_smbus_read_i2c_block_data(priv->client, reg, + len, val); + if (ret) + return ret; + + offset += len; + val += len; + bytes -= len; + } + + return 0; +} + +static int abx80x_nvmem_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + return abx80x_nvmem_xfer(priv, offset, val, bytes, false); +} + +static int abx80x_nvmem_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + return abx80x_nvmem_xfer(priv, offset, val, bytes, true); +} + +static int abx80x_setup_nvmem(struct abx80x_priv *priv) +{ + struct nvmem_config config = { + .type = NVMEM_TYPE_BATTERY_BACKED, + .reg_read = abx80x_nvmem_read, + .reg_write = abx80x_nvmem_write, + .size = ABX8XX_RAM_SIZE, + .priv = priv, + }; + + return devm_rtc_nvmem_register(priv->rtc, &config); +} + static const struct i2c_device_id abx80x_id[] = { { "abx80x", ABX80X }, { "ab0801", AB0801 }, @@ -840,6 +913,10 @@ static int abx80x_probe(struct i2c_client *client) return err; } + err = abx80x_setup_nvmem(priv); + if (err) + return err; + if (client->irq > 0) { dev_info(&client->dev, "IRQ %d supplied\n", client->irq); err = devm_request_threaded_irq(&client->dev, client->irq, NULL, |