drivers/i2c/busses/i2c-mpc.c

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

File Facts

System
Linux kernel
Corpus path
drivers/i2c/busses/i2c-mpc.c
Extension
.c
Size
24648 bytes
Lines
955
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 mpc_i2c {
	struct device *dev;
	void __iomem *base;
	u32 interrupt;
	wait_queue_head_t waitq;
	spinlock_t lock;
	struct i2c_adapter adap;
	int irq;
	u32 real_clk;
	u8 fdr, dfsrr;
	u32 cntl_bits;
	enum mpc_i2c_action action;
	struct i2c_msg *msgs;
	int num_msgs;
	int curr_msg;
	u32 byte_posn;
	u32 block;
	int rc;
	int expect_rxack;
	bool has_errata_A004447;
};

struct mpc_i2c_divider {
	u16 divider;
	u16 fdr;	/* including dfsrr */
};

struct mpc_i2c_data {
	void (*setup)(struct device_node *node, struct mpc_i2c *i2c, u32 clock);
};

static inline void writeccr(struct mpc_i2c *i2c, u32 x)
{
	writeb(x, i2c->base + MPC_I2C_CR);
}

/* Sometimes 9th clock pulse isn't generated, and target doesn't release
 * the bus, because it wants to send ACK.
 * Following sequence of enabling/disabling and sending start/stop generates
 * the 9 pulses, each with a START then ending with STOP, so it's all OK.
 */
static void mpc_i2c_fixup(struct mpc_i2c *i2c)
{
	int k;
	unsigned long flags;

	for (k = 9; k; k--) {
		writeccr(i2c, 0);
		writeb(0, i2c->base + MPC_I2C_SR); /* clear any status bits */
		writeccr(i2c, CCR_MEN | CCR_MSTA); /* START */
		readb(i2c->base + MPC_I2C_DR); /* init xfer */
		udelay(15); /* let it hit the bus */
		local_irq_save(flags); /* should not be delayed further */
		writeccr(i2c, CCR_MEN | CCR_MSTA | CCR_RSTA); /* delay SDA */
		readb(i2c->base + MPC_I2C_DR);
		if (k != 1)
			udelay(5);
		local_irq_restore(flags);
	}
	writeccr(i2c, CCR_MEN); /* Initiate STOP */
	readb(i2c->base + MPC_I2C_DR);
	udelay(15); /* Let STOP propagate */
	writeccr(i2c, 0);
}

static int i2c_mpc_wait_sr(struct mpc_i2c *i2c, int mask)
{
	void __iomem *addr = i2c->base + MPC_I2C_SR;
	u8 val;

	return readb_poll_timeout(addr, val, val & mask, 0, 100);
}

/*
 * Workaround for Erratum A004447. From the P2040CE Rev Q
 *
 * 1.  Set up the frequency divider and sampling rate.
 * 2.  I2CCR - a0h
 * 3.  Poll for I2CSR[MBB] to get set.
 * 4.  If I2CSR[MAL] is set (an indication that SDA is stuck low), then go to
 *     step 5. If MAL is not set, then go to step 13.
 * 5.  I2CCR - 00h
 * 6.  I2CCR - 22h
 * 7.  I2CCR - a2h
 * 8.  Poll for I2CSR[MBB] to get set.
 * 9.  Issue read to I2CDR.
 * 10. Poll for I2CSR[MIF] to be set.
 * 11. I2CCR - 82h
 * 12. Workaround complete. Skip the next steps.
 * 13. Issue read to I2CDR.

Annotation

Implementation Notes