1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (c) 2023 Code Construct 4 * 5 * Author: Jeremy Kerr <jk@codeconstruct.com.au> 6 */ 7 8 #include <linux/mfd/syscon.h> 9 #include <linux/module.h> 10 #include <linux/of.h> 11 #include <linux/platform_device.h> 12 #include <linux/regmap.h> 13 14 #include "dw-i3c-master.h" 15 16 /* AST2600-specific global register set */ 17 #define AST2600_I3CG_REG0(idx) (((idx) * 4 * 4) + 0x10) 18 #define AST2600_I3CG_REG1(idx) (((idx) * 4 * 4) + 0x14) 19 20 #define AST2600_I3CG_REG0_SDA_PULLUP_EN_MASK GENMASK(29, 28) 21 #define AST2600_I3CG_REG0_SDA_PULLUP_EN_2K (0x0 << 28) 22 #define AST2600_I3CG_REG0_SDA_PULLUP_EN_750 (0x2 << 28) 23 #define AST2600_I3CG_REG0_SDA_PULLUP_EN_545 (0x3 << 28) 24 25 #define AST2600_I3CG_REG1_I2C_MODE BIT(0) 26 #define AST2600_I3CG_REG1_TEST_MODE BIT(1) 27 #define AST2600_I3CG_REG1_ACT_MODE_MASK GENMASK(3, 2) 28 #define AST2600_I3CG_REG1_ACT_MODE(x) (((x) << 2) & AST2600_I3CG_REG1_ACT_MODE_MASK) 29 #define AST2600_I3CG_REG1_PENDING_INT_MASK GENMASK(7, 4) 30 #define AST2600_I3CG_REG1_PENDING_INT(x) (((x) << 4) & AST2600_I3CG_REG1_PENDING_INT_MASK) 31 #define AST2600_I3CG_REG1_SA_MASK GENMASK(14, 8) 32 #define AST2600_I3CG_REG1_SA(x) (((x) << 8) & AST2600_I3CG_REG1_SA_MASK) 33 #define AST2600_I3CG_REG1_SA_EN BIT(15) 34 #define AST2600_I3CG_REG1_INST_ID_MASK GENMASK(19, 16) 35 #define AST2600_I3CG_REG1_INST_ID(x) (((x) << 16) & AST2600_I3CG_REG1_INST_ID_MASK) 36 37 #define AST2600_DEFAULT_SDA_PULLUP_OHMS 2000 38 39 #define DEV_ADDR_TABLE_IBI_PEC BIT(11) 40 41 struct ast2600_i3c { 42 struct dw_i3c_master dw; 43 struct regmap *global_regs; 44 unsigned int global_idx; 45 unsigned int sda_pullup; 46 }; 47 48 static struct ast2600_i3c *to_ast2600_i3c(struct dw_i3c_master *dw) 49 { 50 return container_of(dw, struct ast2600_i3c, dw); 51 } 52 53 static int ast2600_i3c_pullup_to_reg(unsigned int ohms, u32 *regp) 54 { 55 u32 reg; 56 57 switch (ohms) { 58 case 2000: 59 reg = AST2600_I3CG_REG0_SDA_PULLUP_EN_2K; 60 break; 61 case 750: 62 reg = AST2600_I3CG_REG0_SDA_PULLUP_EN_750; 63 break; 64 case 545: 65 reg = AST2600_I3CG_REG0_SDA_PULLUP_EN_545; 66 break; 67 default: 68 return -EINVAL; 69 } 70 71 if (regp) 72 *regp = reg; 73 74 return 0; 75 } 76 77 static int ast2600_i3c_init(struct dw_i3c_master *dw) 78 { 79 struct ast2600_i3c *i3c = to_ast2600_i3c(dw); 80 u32 reg = 0; 81 int rc; 82 83 /* reg0: set SDA pullup values */ 84 rc = ast2600_i3c_pullup_to_reg(i3c->sda_pullup, ®); 85 if (rc) 86 return rc; 87 88 rc = regmap_write(i3c->global_regs, 89 AST2600_I3CG_REG0(i3c->global_idx), reg); 90 if (rc) 91 return rc; 92 93 /* reg1: set up the instance id, but leave everything else disabled, 94 * as it's all for client mode 95 */ 96 reg = AST2600_I3CG_REG1_INST_ID(i3c->global_idx); 97 rc = regmap_write(i3c->global_regs, 98 AST2600_I3CG_REG1(i3c->global_idx), reg); 99 100 return rc; 101 } 102 103 static void ast2600_i3c_set_dat_ibi(struct dw_i3c_master *i3c, 104 struct i3c_dev_desc *dev, 105 bool enable, u32 *dat) 106 { 107 /* 108 * The ast2600 i3c controller will lock up on receiving 4n+1-byte IBIs 109 * if the PEC is disabled. We have no way to restrict the length of 110 * IBIs sent to the controller, so we need to unconditionally enable 111 * PEC checking, which means we drop a byte of payload data 112 */ 113 if (enable && dev->info.bcr & I3C_BCR_IBI_PAYLOAD) { 114 dev_warn_once(&i3c->base.dev, 115 "Enabling PEC workaround. IBI payloads will be truncated\n"); 116 *dat |= DEV_ADDR_TABLE_IBI_PEC; 117 } 118 } 119 120 static const struct dw_i3c_platform_ops ast2600_i3c_ops = { 121 .init = ast2600_i3c_init, 122 .set_dat_ibi = ast2600_i3c_set_dat_ibi, 123 }; 124 125 static int ast2600_i3c_probe(struct platform_device *pdev) 126 { 127 struct device_node *np = pdev->dev.of_node; 128 struct of_phandle_args gspec; 129 struct ast2600_i3c *i3c; 130 int rc; 131 132 i3c = devm_kzalloc(&pdev->dev, sizeof(*i3c), GFP_KERNEL); 133 if (!i3c) 134 return -ENOMEM; 135 136 rc = of_parse_phandle_with_fixed_args(np, "aspeed,global-regs", 1, 0, 137 &gspec); 138 if (rc) 139 return -ENODEV; 140 141 i3c->global_regs = syscon_node_to_regmap(gspec.np); 142 of_node_put(gspec.np); 143 144 if (IS_ERR(i3c->global_regs)) 145 return PTR_ERR(i3c->global_regs); 146 147 i3c->global_idx = gspec.args[0]; 148 149 rc = of_property_read_u32(np, "sda-pullup-ohms", &i3c->sda_pullup); 150 if (rc) 151 i3c->sda_pullup = AST2600_DEFAULT_SDA_PULLUP_OHMS; 152 153 rc = ast2600_i3c_pullup_to_reg(i3c->sda_pullup, NULL); 154 if (rc) 155 dev_err(&pdev->dev, "invalid sda-pullup value %d\n", 156 i3c->sda_pullup); 157 158 i3c->dw.platform_ops = &ast2600_i3c_ops; 159 i3c->dw.ibi_capable = true; 160 return dw_i3c_common_probe(&i3c->dw, pdev); 161 } 162 163 static void ast2600_i3c_remove(struct platform_device *pdev) 164 { 165 struct dw_i3c_master *dw_i3c = platform_get_drvdata(pdev); 166 167 dw_i3c_common_remove(dw_i3c); 168 } 169 170 static const struct of_device_id ast2600_i3c_master_of_match[] = { 171 { .compatible = "aspeed,ast2600-i3c", }, 172 {}, 173 }; 174 MODULE_DEVICE_TABLE(of, ast2600_i3c_master_of_match); 175 176 static struct platform_driver ast2600_i3c_driver = { 177 .probe = ast2600_i3c_probe, 178 .remove_new = ast2600_i3c_remove, 179 .driver = { 180 .name = "ast2600-i3c-master", 181 .of_match_table = ast2600_i3c_master_of_match, 182 }, 183 }; 184 module_platform_driver(ast2600_i3c_driver); 185 186 MODULE_AUTHOR("Jeremy Kerr <jk@codeconstruct.com.au>"); 187 MODULE_DESCRIPTION("ASPEED AST2600 I3C driver"); 188 MODULE_LICENSE("GPL"); 189