drivers/i2c/busses/i2c-meson.c

Source file repositories/reference/linux-study-clean/drivers/i2c/busses/i2c-meson.c

File Facts

System
Linux kernel
Corpus path
drivers/i2c/busses/i2c-meson.c
Extension
.c
Size
14746 bytes
Lines
580
Domain
Driver Families
Bucket
drivers/i2c
Inferred role
Driver Families: implementation source
Status
source implementation candidate

Why This File Exists

Repeatable hardware-adapter layer. Deep compatibility for every driver is out of scope; this atlas records patterns, probe lifecycles, bus glue, IRQ/DMA usage, and links back to core abstractions.

Dependency Surface

Detected Declarations

Annotated Snippet

struct meson_i2c {
	struct i2c_adapter	adap;
	struct device		*dev;
	void __iomem		*regs;
	struct clk		*clk;

	struct i2c_msg		*msg;
	int			state;
	bool			last;
	int			count;
	int			pos;
	int			error;

	spinlock_t		lock;
	struct completion	done;
	u32			tokens[2];
	int			num_tokens;

	const struct meson_i2c_data *data;
};

struct meson_i2c_data {
	void (*set_clk_div)(struct meson_i2c *i2c, unsigned int freq);
};

static void meson_i2c_set_mask(struct meson_i2c *i2c, int reg, u32 mask,
			       u32 val)
{
	u32 data;

	data = readl(i2c->regs + reg);
	data &= ~mask;
	data |= val & mask;
	writel(data, i2c->regs + reg);
}

static void meson_i2c_reset_tokens(struct meson_i2c *i2c)
{
	i2c->tokens[0] = 0;
	i2c->tokens[1] = 0;
	i2c->num_tokens = 0;
}

static void meson_i2c_add_token(struct meson_i2c *i2c, int token)
{
	if (i2c->num_tokens < 8)
		i2c->tokens[0] |= (token & 0xf) << (i2c->num_tokens * 4);
	else
		i2c->tokens[1] |= (token & 0xf) << ((i2c->num_tokens % 8) * 4);

	i2c->num_tokens++;
}

static void meson_gxbb_axg_i2c_set_clk_div(struct meson_i2c *i2c, unsigned int freq)
{
	unsigned long clk_rate = clk_get_rate(i2c->clk);
	unsigned int div_h, div_l;

	/* According to I2C-BUS Spec 2.1, in FAST-MODE, the minimum LOW period is 1.3uS, and
	 * minimum HIGH is least 0.6us.
	 * For 400000 freq, the period is 2.5us. To keep within the specs, give 40% of period to
	 * HIGH and 60% to LOW. This means HIGH at 1.0us and LOW 1.5us.
	 * The same applies for Fast-mode plus, where LOW is 0.5us and HIGH is 0.26us.
	 * Duty = H/(H + L) = 2/5
	 */
	if (freq <= I2C_MAX_STANDARD_MODE_FREQ) {
		div_h = DIV_ROUND_UP(clk_rate, freq);
		div_l = DIV_ROUND_UP(div_h, 4);
		div_h = DIV_ROUND_UP(div_h, 2) - FILTER_DELAY;
	} else {
		div_h = DIV_ROUND_UP(clk_rate * 2, freq * 5) - FILTER_DELAY;
		div_l = DIV_ROUND_UP(clk_rate * 3, freq * 5 * 2);
	}

	/* clock divider has 12 bits */
	if (div_h > GENMASK(11, 0)) {
		dev_err(i2c->dev, "requested bus frequency too low\n");
		div_h = GENMASK(11, 0);
	}
	if (div_l > GENMASK(11, 0)) {
		dev_err(i2c->dev, "requested bus frequency too low\n");
		div_l = GENMASK(11, 0);
	}

	meson_i2c_set_mask(i2c, REG_CTRL, REG_CTRL_CLKDIV_MASK,
			   FIELD_PREP(REG_CTRL_CLKDIV_MASK, div_h & GENMASK(9, 0)));

	meson_i2c_set_mask(i2c, REG_CTRL, REG_CTRL_CLKDIVEXT_MASK,
			   FIELD_PREP(REG_CTRL_CLKDIVEXT_MASK, div_h >> 10));

Annotation

Implementation Notes