| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2019 Stephan Gerhold |
| * |
| * Adapted from old U-Boot and Linux kernel implementation: |
| * Copyright (C) STMicroelectronics 2009 |
| * Copyright (C) ST-Ericsson SA 2010 |
| */ |
| |
| #include <dm.h> |
| #include <regmap.h> |
| #include <syscon.h> |
| #include <linux/bitops.h> |
| #include <linux/err.h> |
| #include <power/ab8500.h> |
| #include <power/pmic.h> |
| |
| /* CPU mailbox registers */ |
| #define PRCM_MBOX_CPU_VAL 0x0fc |
| #define PRCM_MBOX_CPU_SET 0x100 |
| #define PRCM_MBOX_CPU_CLR 0x104 |
| |
| #define PRCM_ARM_IT1_CLR 0x48C |
| #define PRCM_ARM_IT1_VAL 0x494 |
| |
| #define PRCM_TCDM_RANGE 2 |
| #define PRCM_REQ_MB5 0xE44 |
| #define PRCM_ACK_MB5 0xDF4 |
| #define _PRCM_MBOX_HEADER 0xFE8 |
| #define PRCM_MBOX_HEADER_REQ_MB5 (_PRCM_MBOX_HEADER + 0x5) |
| #define PRCMU_I2C_MBOX_BIT BIT(5) |
| |
| /* Mailbox 5 Requests */ |
| #define PRCM_REQ_MB5_I2C_SLAVE_OP (PRCM_REQ_MB5 + 0x0) |
| #define PRCM_REQ_MB5_I2C_HW_BITS (PRCM_REQ_MB5 + 0x1) |
| #define PRCM_REQ_MB5_I2C_REG (PRCM_REQ_MB5 + 0x2) |
| #define PRCM_REQ_MB5_I2C_VAL (PRCM_REQ_MB5 + 0x3) |
| #define PRCMU_I2C(bank) (((bank) << 1) | BIT(6)) |
| #define PRCMU_I2C_WRITE 0 |
| #define PRCMU_I2C_READ 1 |
| #define PRCMU_I2C_STOP_EN BIT(3) |
| |
| /* Mailbox 5 ACKs */ |
| #define PRCM_ACK_MB5_I2C_STATUS (PRCM_ACK_MB5 + 0x1) |
| #define PRCM_ACK_MB5_I2C_VAL (PRCM_ACK_MB5 + 0x3) |
| #define PRCMU_I2C_WR_OK 0x1 |
| #define PRCMU_I2C_RD_OK 0x2 |
| |
| /* AB8500 version registers */ |
| #define AB8500_MISC_REV_REG AB8500_MISC(0x80) |
| #define AB8500_MISC_IC_NAME_REG AB8500_MISC(0x82) |
| |
| struct ab8500_priv { |
| struct ab8500 ab8500; |
| struct regmap *regmap; |
| }; |
| |
| static inline int prcmu_tcdm_readb(struct regmap *map, uint offset, u8 *valp) |
| { |
| return regmap_raw_read_range(map, PRCM_TCDM_RANGE, offset, |
| valp, sizeof(*valp)); |
| } |
| |
| static inline int prcmu_tcdm_writeb(struct regmap *map, uint offset, u8 val) |
| { |
| return regmap_raw_write_range(map, PRCM_TCDM_RANGE, offset, |
| &val, sizeof(val)); |
| } |
| |
| static int prcmu_wait_i2c_mbx_ready(struct ab8500_priv *priv) |
| { |
| uint val; |
| int ret; |
| |
| ret = regmap_read(priv->regmap, PRCM_ARM_IT1_VAL, &val); |
| if (ret) |
| return ret; |
| |
| if (val & PRCMU_I2C_MBOX_BIT) { |
| printf("ab8500: warning: PRCMU i2c mailbox was not acked\n"); |
| /* clear mailbox 5 ack irq */ |
| ret = regmap_write(priv->regmap, PRCM_ARM_IT1_CLR, |
| PRCMU_I2C_MBOX_BIT); |
| if (ret) |
| return ret; |
| } |
| |
| /* wait for on-going transaction, use 1s timeout */ |
| return regmap_read_poll_timeout(priv->regmap, PRCM_MBOX_CPU_VAL, val, |
| !(val & PRCMU_I2C_MBOX_BIT), 0, 1000); |
| } |
| |
| static int prcmu_wait_i2c_mbx_done(struct ab8500_priv *priv) |
| { |
| uint val; |
| int ret; |
| |
| /* set interrupt to XP70 */ |
| ret = regmap_write(priv->regmap, PRCM_MBOX_CPU_SET, PRCMU_I2C_MBOX_BIT); |
| if (ret) |
| return ret; |
| |
| /* wait for mailbox 5 (i2c) ack, use 1s timeout */ |
| return regmap_read_poll_timeout(priv->regmap, PRCM_ARM_IT1_VAL, val, |
| (val & PRCMU_I2C_MBOX_BIT), 0, 1000); |
| } |
| |
| static int ab8500_transfer(struct udevice *dev, uint bank_reg, u8 *val, |
| u8 op, u8 expected_status) |
| { |
| struct ab8500_priv *priv = dev_get_priv(dev); |
| u8 reg = bank_reg & 0xff; |
| u8 bank = bank_reg >> 8; |
| u8 status; |
| int ret; |
| |
| ret = prcmu_wait_i2c_mbx_ready(priv); |
| if (ret) |
| return ret; |
| |
| ret = prcmu_tcdm_writeb(priv->regmap, PRCM_MBOX_HEADER_REQ_MB5, 0); |
| if (ret) |
| return ret; |
| ret = prcmu_tcdm_writeb(priv->regmap, PRCM_REQ_MB5_I2C_SLAVE_OP, |
| PRCMU_I2C(bank) | op); |
| if (ret) |
| return ret; |
| ret = prcmu_tcdm_writeb(priv->regmap, PRCM_REQ_MB5_I2C_HW_BITS, |
| PRCMU_I2C_STOP_EN); |
| if (ret) |
| return ret; |
| ret = prcmu_tcdm_writeb(priv->regmap, PRCM_REQ_MB5_I2C_REG, reg); |
| if (ret) |
| return ret; |
| ret = prcmu_tcdm_writeb(priv->regmap, PRCM_REQ_MB5_I2C_VAL, *val); |
| if (ret) |
| return ret; |
| |
| ret = prcmu_wait_i2c_mbx_done(priv); |
| if (ret) { |
| printf("%s: mailbox request timed out\n", __func__); |
| return ret; |
| } |
| |
| /* read transfer result */ |
| ret = prcmu_tcdm_readb(priv->regmap, PRCM_ACK_MB5_I2C_STATUS, &status); |
| if (ret) |
| return ret; |
| ret = prcmu_tcdm_readb(priv->regmap, PRCM_ACK_MB5_I2C_VAL, val); |
| if (ret) |
| return ret; |
| |
| /* |
| * Clear mailbox 5 ack irq. Note that the transfer is already complete |
| * here so checking for errors does not make sense. Clearing the irq |
| * will be retried in prcmu_wait_i2c_mbx_ready() on the next transfer. |
| */ |
| regmap_write(priv->regmap, PRCM_ARM_IT1_CLR, PRCMU_I2C_MBOX_BIT); |
| |
| if (status != expected_status) { |
| /* |
| * AB8500 does not have the AB8500_MISC_IC_NAME_REG register, |
| * but we need to try reading it to detect AB8505. |
| * In case of an error, assume that we have AB8500. |
| */ |
| if (op == PRCMU_I2C_READ && bank_reg == AB8500_MISC_IC_NAME_REG) { |
| *val = AB8500_VERSION_AB8500; |
| return 0; |
| } |
| |
| printf("%s: return status %d\n", __func__, status); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int ab8500_reg_count(struct udevice *dev) |
| { |
| return AB8500_NUM_REGISTERS; |
| } |
| |
| static int ab8500_read(struct udevice *dev, uint reg, uint8_t *buf, int len) |
| { |
| int ret; |
| |
| if (len != 1) |
| return -EINVAL; |
| |
| *buf = 0; |
| ret = ab8500_transfer(dev, reg, buf, PRCMU_I2C_READ, PRCMU_I2C_RD_OK); |
| if (ret) { |
| printf("%s failed: %d\n", __func__, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int ab8500_write(struct udevice *dev, uint reg, const uint8_t *buf, int len) |
| { |
| int ret; |
| u8 val; |
| |
| if (len != 1) |
| return -EINVAL; |
| |
| val = *buf; |
| ret = ab8500_transfer(dev, reg, &val, PRCMU_I2C_WRITE, PRCMU_I2C_WR_OK); |
| if (ret) { |
| printf("%s failed: %d\n", __func__, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static struct dm_pmic_ops ab8500_ops = { |
| .reg_count = ab8500_reg_count, |
| .read = ab8500_read, |
| .write = ab8500_write, |
| }; |
| |
| static int ab8500_probe(struct udevice *dev) |
| { |
| struct ab8500_priv *priv = dev_get_priv(dev); |
| int ret; |
| |
| /* get regmap from the PRCMU parent device (syscon in U-Boot) */ |
| priv->regmap = syscon_get_regmap(dev->parent); |
| if (IS_ERR(priv->regmap)) |
| return PTR_ERR(priv->regmap); |
| |
| ret = pmic_reg_read(dev, AB8500_MISC_IC_NAME_REG); |
| if (ret < 0) { |
| printf("ab8500: failed to read chip version: %d\n", ret); |
| return ret; |
| } |
| priv->ab8500.version = ret; |
| |
| ret = pmic_reg_read(dev, AB8500_MISC_REV_REG); |
| if (ret < 0) { |
| printf("ab8500: failed to read chip id: %d\n", ret); |
| return ret; |
| } |
| priv->ab8500.chip_id = ret; |
| |
| debug("ab8500: version: %#x, chip id: %#x\n", |
| priv->ab8500.version, priv->ab8500.chip_id); |
| |
| return 0; |
| } |
| |
| static const struct udevice_id ab8500_ids[] = { |
| { .compatible = "stericsson,ab8500" }, |
| { } |
| }; |
| |
| U_BOOT_DRIVER(pmic_ab8500) = { |
| .name = "pmic_ab8500", |
| .id = UCLASS_PMIC, |
| .of_match = ab8500_ids, |
| .bind = dm_scan_fdt_dev, |
| .probe = ab8500_probe, |
| .ops = &ab8500_ops, |
| .priv_auto = sizeof(struct ab8500_priv), |
| }; |