162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2023 Code Construct 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Author: Jeremy Kerr <jk@codeconstruct.com.au> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/mfd/syscon.h> 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/of.h> 1162306a36Sopenharmony_ci#include <linux/platform_device.h> 1262306a36Sopenharmony_ci#include <linux/regmap.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include "dw-i3c-master.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci/* AST2600-specific global register set */ 1762306a36Sopenharmony_ci#define AST2600_I3CG_REG0(idx) (((idx) * 4 * 4) + 0x10) 1862306a36Sopenharmony_ci#define AST2600_I3CG_REG1(idx) (((idx) * 4 * 4) + 0x14) 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define AST2600_I3CG_REG0_SDA_PULLUP_EN_MASK GENMASK(29, 28) 2162306a36Sopenharmony_ci#define AST2600_I3CG_REG0_SDA_PULLUP_EN_2K (0x0 << 28) 2262306a36Sopenharmony_ci#define AST2600_I3CG_REG0_SDA_PULLUP_EN_750 (0x2 << 28) 2362306a36Sopenharmony_ci#define AST2600_I3CG_REG0_SDA_PULLUP_EN_545 (0x3 << 28) 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define AST2600_I3CG_REG1_I2C_MODE BIT(0) 2662306a36Sopenharmony_ci#define AST2600_I3CG_REG1_TEST_MODE BIT(1) 2762306a36Sopenharmony_ci#define AST2600_I3CG_REG1_ACT_MODE_MASK GENMASK(3, 2) 2862306a36Sopenharmony_ci#define AST2600_I3CG_REG1_ACT_MODE(x) (((x) << 2) & AST2600_I3CG_REG1_ACT_MODE_MASK) 2962306a36Sopenharmony_ci#define AST2600_I3CG_REG1_PENDING_INT_MASK GENMASK(7, 4) 3062306a36Sopenharmony_ci#define AST2600_I3CG_REG1_PENDING_INT(x) (((x) << 4) & AST2600_I3CG_REG1_PENDING_INT_MASK) 3162306a36Sopenharmony_ci#define AST2600_I3CG_REG1_SA_MASK GENMASK(14, 8) 3262306a36Sopenharmony_ci#define AST2600_I3CG_REG1_SA(x) (((x) << 8) & AST2600_I3CG_REG1_SA_MASK) 3362306a36Sopenharmony_ci#define AST2600_I3CG_REG1_SA_EN BIT(15) 3462306a36Sopenharmony_ci#define AST2600_I3CG_REG1_INST_ID_MASK GENMASK(19, 16) 3562306a36Sopenharmony_ci#define AST2600_I3CG_REG1_INST_ID(x) (((x) << 16) & AST2600_I3CG_REG1_INST_ID_MASK) 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#define AST2600_DEFAULT_SDA_PULLUP_OHMS 2000 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci#define DEV_ADDR_TABLE_IBI_PEC BIT(11) 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistruct ast2600_i3c { 4262306a36Sopenharmony_ci struct dw_i3c_master dw; 4362306a36Sopenharmony_ci struct regmap *global_regs; 4462306a36Sopenharmony_ci unsigned int global_idx; 4562306a36Sopenharmony_ci unsigned int sda_pullup; 4662306a36Sopenharmony_ci}; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic struct ast2600_i3c *to_ast2600_i3c(struct dw_i3c_master *dw) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci return container_of(dw, struct ast2600_i3c, dw); 5162306a36Sopenharmony_ci} 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic int ast2600_i3c_pullup_to_reg(unsigned int ohms, u32 *regp) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci u32 reg; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci switch (ohms) { 5862306a36Sopenharmony_ci case 2000: 5962306a36Sopenharmony_ci reg = AST2600_I3CG_REG0_SDA_PULLUP_EN_2K; 6062306a36Sopenharmony_ci break; 6162306a36Sopenharmony_ci case 750: 6262306a36Sopenharmony_ci reg = AST2600_I3CG_REG0_SDA_PULLUP_EN_750; 6362306a36Sopenharmony_ci break; 6462306a36Sopenharmony_ci case 545: 6562306a36Sopenharmony_ci reg = AST2600_I3CG_REG0_SDA_PULLUP_EN_545; 6662306a36Sopenharmony_ci break; 6762306a36Sopenharmony_ci default: 6862306a36Sopenharmony_ci return -EINVAL; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci if (regp) 7262306a36Sopenharmony_ci *regp = reg; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci return 0; 7562306a36Sopenharmony_ci} 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic int ast2600_i3c_init(struct dw_i3c_master *dw) 7862306a36Sopenharmony_ci{ 7962306a36Sopenharmony_ci struct ast2600_i3c *i3c = to_ast2600_i3c(dw); 8062306a36Sopenharmony_ci u32 reg = 0; 8162306a36Sopenharmony_ci int rc; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci /* reg0: set SDA pullup values */ 8462306a36Sopenharmony_ci rc = ast2600_i3c_pullup_to_reg(i3c->sda_pullup, ®); 8562306a36Sopenharmony_ci if (rc) 8662306a36Sopenharmony_ci return rc; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci rc = regmap_write(i3c->global_regs, 8962306a36Sopenharmony_ci AST2600_I3CG_REG0(i3c->global_idx), reg); 9062306a36Sopenharmony_ci if (rc) 9162306a36Sopenharmony_ci return rc; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci /* reg1: set up the instance id, but leave everything else disabled, 9462306a36Sopenharmony_ci * as it's all for client mode 9562306a36Sopenharmony_ci */ 9662306a36Sopenharmony_ci reg = AST2600_I3CG_REG1_INST_ID(i3c->global_idx); 9762306a36Sopenharmony_ci rc = regmap_write(i3c->global_regs, 9862306a36Sopenharmony_ci AST2600_I3CG_REG1(i3c->global_idx), reg); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci return rc; 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic void ast2600_i3c_set_dat_ibi(struct dw_i3c_master *i3c, 10462306a36Sopenharmony_ci struct i3c_dev_desc *dev, 10562306a36Sopenharmony_ci bool enable, u32 *dat) 10662306a36Sopenharmony_ci{ 10762306a36Sopenharmony_ci /* 10862306a36Sopenharmony_ci * The ast2600 i3c controller will lock up on receiving 4n+1-byte IBIs 10962306a36Sopenharmony_ci * if the PEC is disabled. We have no way to restrict the length of 11062306a36Sopenharmony_ci * IBIs sent to the controller, so we need to unconditionally enable 11162306a36Sopenharmony_ci * PEC checking, which means we drop a byte of payload data 11262306a36Sopenharmony_ci */ 11362306a36Sopenharmony_ci if (enable && dev->info.bcr & I3C_BCR_IBI_PAYLOAD) { 11462306a36Sopenharmony_ci dev_warn_once(&i3c->base.dev, 11562306a36Sopenharmony_ci "Enabling PEC workaround. IBI payloads will be truncated\n"); 11662306a36Sopenharmony_ci *dat |= DEV_ADDR_TABLE_IBI_PEC; 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic const struct dw_i3c_platform_ops ast2600_i3c_ops = { 12162306a36Sopenharmony_ci .init = ast2600_i3c_init, 12262306a36Sopenharmony_ci .set_dat_ibi = ast2600_i3c_set_dat_ibi, 12362306a36Sopenharmony_ci}; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_cistatic int ast2600_i3c_probe(struct platform_device *pdev) 12662306a36Sopenharmony_ci{ 12762306a36Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 12862306a36Sopenharmony_ci struct of_phandle_args gspec; 12962306a36Sopenharmony_ci struct ast2600_i3c *i3c; 13062306a36Sopenharmony_ci int rc; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci i3c = devm_kzalloc(&pdev->dev, sizeof(*i3c), GFP_KERNEL); 13362306a36Sopenharmony_ci if (!i3c) 13462306a36Sopenharmony_ci return -ENOMEM; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci rc = of_parse_phandle_with_fixed_args(np, "aspeed,global-regs", 1, 0, 13762306a36Sopenharmony_ci &gspec); 13862306a36Sopenharmony_ci if (rc) 13962306a36Sopenharmony_ci return -ENODEV; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci i3c->global_regs = syscon_node_to_regmap(gspec.np); 14262306a36Sopenharmony_ci of_node_put(gspec.np); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci if (IS_ERR(i3c->global_regs)) 14562306a36Sopenharmony_ci return PTR_ERR(i3c->global_regs); 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci i3c->global_idx = gspec.args[0]; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci rc = of_property_read_u32(np, "sda-pullup-ohms", &i3c->sda_pullup); 15062306a36Sopenharmony_ci if (rc) 15162306a36Sopenharmony_ci i3c->sda_pullup = AST2600_DEFAULT_SDA_PULLUP_OHMS; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci rc = ast2600_i3c_pullup_to_reg(i3c->sda_pullup, NULL); 15462306a36Sopenharmony_ci if (rc) 15562306a36Sopenharmony_ci dev_err(&pdev->dev, "invalid sda-pullup value %d\n", 15662306a36Sopenharmony_ci i3c->sda_pullup); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci i3c->dw.platform_ops = &ast2600_i3c_ops; 15962306a36Sopenharmony_ci i3c->dw.ibi_capable = true; 16062306a36Sopenharmony_ci return dw_i3c_common_probe(&i3c->dw, pdev); 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic void ast2600_i3c_remove(struct platform_device *pdev) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci struct dw_i3c_master *dw_i3c = platform_get_drvdata(pdev); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci dw_i3c_common_remove(dw_i3c); 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cistatic const struct of_device_id ast2600_i3c_master_of_match[] = { 17162306a36Sopenharmony_ci { .compatible = "aspeed,ast2600-i3c", }, 17262306a36Sopenharmony_ci {}, 17362306a36Sopenharmony_ci}; 17462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ast2600_i3c_master_of_match); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic struct platform_driver ast2600_i3c_driver = { 17762306a36Sopenharmony_ci .probe = ast2600_i3c_probe, 17862306a36Sopenharmony_ci .remove_new = ast2600_i3c_remove, 17962306a36Sopenharmony_ci .driver = { 18062306a36Sopenharmony_ci .name = "ast2600-i3c-master", 18162306a36Sopenharmony_ci .of_match_table = ast2600_i3c_master_of_match, 18262306a36Sopenharmony_ci }, 18362306a36Sopenharmony_ci}; 18462306a36Sopenharmony_cimodule_platform_driver(ast2600_i3c_driver); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ciMODULE_AUTHOR("Jeremy Kerr <jk@codeconstruct.com.au>"); 18762306a36Sopenharmony_ciMODULE_DESCRIPTION("ASPEED AST2600 I3C driver"); 18862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 189