// 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 #include #include #include #include #include #include #include /* 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), };