summaryrefslogtreecommitdiff
path: root/drivers/i3c/master/ast2600-i3c-master.c
blob: 84942dbb6f8028cc5d02717912e6174fa0228afe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2023 Code Construct
 *
 * Author: Jeremy Kerr <jk@codeconstruct.com.au>
 */

#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>

#include "dw-i3c-master.h"

/* AST2600-specific global register set */
#define AST2600_I3CG_REG0(idx)	(((idx) * 4 * 4) + 0x10)
#define AST2600_I3CG_REG1(idx)	(((idx) * 4 * 4) + 0x14)

#define AST2600_I3CG_REG0_SDA_PULLUP_EN_MASK	GENMASK(29, 28)
#define AST2600_I3CG_REG0_SDA_PULLUP_EN_2K	(0x0 << 28)
#define AST2600_I3CG_REG0_SDA_PULLUP_EN_750	(0x2 << 28)
#define AST2600_I3CG_REG0_SDA_PULLUP_EN_545	(0x3 << 28)

#define AST2600_I3CG_REG1_I2C_MODE		BIT(0)
#define AST2600_I3CG_REG1_TEST_MODE		BIT(1)
#define AST2600_I3CG_REG1_ACT_MODE_MASK		GENMASK(3, 2)
#define AST2600_I3CG_REG1_ACT_MODE(x)		(((x) << 2) & AST2600_I3CG_REG1_ACT_MODE_MASK)
#define AST2600_I3CG_REG1_PENDING_INT_MASK	GENMASK(7, 4)
#define AST2600_I3CG_REG1_PENDING_INT(x)	(((x) << 4) & AST2600_I3CG_REG1_PENDING_INT_MASK)
#define AST2600_I3CG_REG1_SA_MASK		GENMASK(14, 8)
#define AST2600_I3CG_REG1_SA(x)			(((x) << 8) & AST2600_I3CG_REG1_SA_MASK)
#define AST2600_I3CG_REG1_SA_EN			BIT(15)
#define AST2600_I3CG_REG1_INST_ID_MASK		GENMASK(19, 16)
#define AST2600_I3CG_REG1_INST_ID(x)		(((x) << 16) & AST2600_I3CG_REG1_INST_ID_MASK)

#define AST2600_DEFAULT_SDA_PULLUP_OHMS		2000

#define DEV_ADDR_TABLE_IBI_PEC			BIT(11)

struct ast2600_i3c {
	struct dw_i3c_master dw;
	struct regmap *global_regs;
	unsigned int global_idx;
	unsigned int sda_pullup;
};

static struct ast2600_i3c *to_ast2600_i3c(struct dw_i3c_master *dw)
{
	return container_of(dw, struct ast2600_i3c, dw);
}

static int ast2600_i3c_pullup_to_reg(unsigned int ohms, u32 *regp)
{
	u32 reg;

	switch (ohms) {
	case 2000:
		reg = AST2600_I3CG_REG0_SDA_PULLUP_EN_2K;
		break;
	case 750:
		reg = AST2600_I3CG_REG0_SDA_PULLUP_EN_750;
		break;
	case 545:
		reg = AST2600_I3CG_REG0_SDA_PULLUP_EN_545;
		break;
	default:
		return -EINVAL;
	}

	if (regp)
		*regp = reg;

	return 0;
}

static int ast2600_i3c_init(struct dw_i3c_master *dw)
{
	struct ast2600_i3c *i3c = to_ast2600_i3c(dw);
	u32 reg = 0;
	int rc;

	/* reg0: set SDA pullup values */
	rc = ast2600_i3c_pullup_to_reg(i3c->sda_pullup, &reg);
	if (rc)
		return rc;

	rc = regmap_write(i3c->global_regs,
			  AST2600_I3CG_REG0(i3c->global_idx), reg);
	if (rc)
		return rc;

	/* reg1: set up the instance id, but leave everything else disabled,
	 * as it's all for client mode
	 */
	reg = AST2600_I3CG_REG1_INST_ID(i3c->global_idx);
	rc = regmap_write(i3c->global_regs,
			  AST2600_I3CG_REG1(i3c->global_idx), reg);

	return rc;
}

static void ast2600_i3c_set_dat_ibi(struct dw_i3c_master *i3c,
				    struct i3c_dev_desc *dev,
				    bool enable, u32 *dat)
{
	/*
	 * The ast2600 i3c controller will lock up on receiving 4n+1-byte IBIs
	 * if the PEC is disabled. We have no way to restrict the length of
	 * IBIs sent to the controller, so we need to unconditionally enable
	 * PEC checking, which means we drop a byte of payload data
	 */
	if (enable && dev->info.bcr & I3C_BCR_IBI_PAYLOAD) {
		dev_warn_once(&i3c->base.dev,
		      "Enabling PEC workaround. IBI payloads will be truncated\n");
		*dat |= DEV_ADDR_TABLE_IBI_PEC;
	}
}

static const struct dw_i3c_platform_ops ast2600_i3c_ops = {
	.init = ast2600_i3c_init,
	.set_dat_ibi = ast2600_i3c_set_dat_ibi,
};

static int ast2600_i3c_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	struct of_phandle_args gspec;
	struct ast2600_i3c *i3c;
	int rc;

	i3c = devm_kzalloc(&pdev->dev, sizeof(*i3c), GFP_KERNEL);
	if (!i3c)
		return -ENOMEM;

	rc = of_parse_phandle_with_fixed_args(np, "aspeed,global-regs", 1, 0,
					      &gspec);
	if (rc)
		return -ENODEV;

	i3c->global_regs = syscon_node_to_regmap(gspec.np);
	of_node_put(gspec.np);

	if (IS_ERR(i3c->global_regs))
		return PTR_ERR(i3c->global_regs);

	i3c->global_idx = gspec.args[0];

	rc = of_property_read_u32(np, "sda-pullup-ohms", &i3c->sda_pullup);
	if (rc)
		i3c->sda_pullup = AST2600_DEFAULT_SDA_PULLUP_OHMS;

	rc = ast2600_i3c_pullup_to_reg(i3c->sda_pullup, NULL);
	if (rc)
		dev_err(&pdev->dev, "invalid sda-pullup value %d\n",
			i3c->sda_pullup);

	i3c->dw.platform_ops = &ast2600_i3c_ops;
	return dw_i3c_common_probe(&i3c->dw, pdev);
}

static void ast2600_i3c_remove(struct platform_device *pdev)
{
	struct dw_i3c_master *dw_i3c = platform_get_drvdata(pdev);

	dw_i3c_common_remove(dw_i3c);
}

static const struct of_device_id ast2600_i3c_master_of_match[] = {
	{ .compatible = "aspeed,ast2600-i3c", },
	{},
};
MODULE_DEVICE_TABLE(of, ast2600_i3c_master_of_match);

static struct platform_driver ast2600_i3c_driver = {
	.probe = ast2600_i3c_probe,
	.remove_new = ast2600_i3c_remove,
	.driver = {
		.name = "ast2600-i3c-master",
		.of_match_table = ast2600_i3c_master_of_match,
	},
};
module_platform_driver(ast2600_i3c_driver);

MODULE_AUTHOR("Jeremy Kerr <jk@codeconstruct.com.au>");
MODULE_DESCRIPTION("ASPEED AST2600 I3C driver");
MODULE_LICENSE("GPL");