1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * i2c slave support for Atmel's AT91 Two-Wire Interface (TWI) |
4 | * |
5 | * Copyright (C) 2017 Juergen Fitschen <me@jue.yt> |
6 | */ |
7 | |
8 | #include <linux/err.h> |
9 | #include <linux/i2c.h> |
10 | #include <linux/interrupt.h> |
11 | #include <linux/pm_runtime.h> |
12 | |
13 | #include "i2c-at91.h" |
14 | |
15 | static irqreturn_t atmel_twi_interrupt_slave(int irq, void *dev_id) |
16 | { |
17 | struct at91_twi_dev *dev = dev_id; |
18 | const unsigned status = at91_twi_read(dev, AT91_TWI_SR); |
19 | const unsigned irqstatus = status & at91_twi_read(dev, AT91_TWI_IMR); |
20 | u8 value; |
21 | |
22 | if (!irqstatus) |
23 | return IRQ_NONE; |
24 | |
25 | /* slave address has been detected on I2C bus */ |
26 | if (irqstatus & AT91_TWI_SVACC) { |
27 | if (status & AT91_TWI_SVREAD) { |
28 | i2c_slave_event(client: dev->slave, |
29 | event: I2C_SLAVE_READ_REQUESTED, val: &value); |
30 | writeb_relaxed(value, dev->base + AT91_TWI_THR); |
31 | at91_twi_write(dev, AT91_TWI_IER, |
32 | AT91_TWI_TXRDY | AT91_TWI_EOSACC); |
33 | } else { |
34 | i2c_slave_event(client: dev->slave, |
35 | event: I2C_SLAVE_WRITE_REQUESTED, val: &value); |
36 | at91_twi_write(dev, AT91_TWI_IER, |
37 | AT91_TWI_RXRDY | AT91_TWI_EOSACC); |
38 | } |
39 | at91_twi_write(dev, AT91_TWI_IDR, AT91_TWI_SVACC); |
40 | } |
41 | |
42 | /* byte transmitted to remote master */ |
43 | if (irqstatus & AT91_TWI_TXRDY) { |
44 | i2c_slave_event(client: dev->slave, event: I2C_SLAVE_READ_PROCESSED, val: &value); |
45 | writeb_relaxed(value, dev->base + AT91_TWI_THR); |
46 | } |
47 | |
48 | /* byte received from remote master */ |
49 | if (irqstatus & AT91_TWI_RXRDY) { |
50 | value = readb_relaxed(dev->base + AT91_TWI_RHR); |
51 | i2c_slave_event(client: dev->slave, event: I2C_SLAVE_WRITE_RECEIVED, val: &value); |
52 | } |
53 | |
54 | /* master sent stop */ |
55 | if (irqstatus & AT91_TWI_EOSACC) { |
56 | at91_twi_write(dev, AT91_TWI_IDR, |
57 | AT91_TWI_TXRDY | AT91_TWI_RXRDY | AT91_TWI_EOSACC); |
58 | at91_twi_write(dev, AT91_TWI_IER, AT91_TWI_SVACC); |
59 | i2c_slave_event(client: dev->slave, event: I2C_SLAVE_STOP, val: &value); |
60 | } |
61 | |
62 | return IRQ_HANDLED; |
63 | } |
64 | |
65 | static int at91_reg_slave(struct i2c_client *slave) |
66 | { |
67 | struct at91_twi_dev *dev = i2c_get_adapdata(adap: slave->adapter); |
68 | |
69 | if (dev->slave) |
70 | return -EBUSY; |
71 | |
72 | if (slave->flags & I2C_CLIENT_TEN) |
73 | return -EAFNOSUPPORT; |
74 | |
75 | /* Make sure twi_clk doesn't get turned off! */ |
76 | pm_runtime_get_sync(dev: dev->dev); |
77 | |
78 | dev->slave = slave; |
79 | dev->smr = AT91_TWI_SMR_SADR(slave->addr); |
80 | |
81 | at91_init_twi_bus(dev); |
82 | at91_twi_write(dev, AT91_TWI_IER, AT91_TWI_SVACC); |
83 | |
84 | dev_info(dev->dev, "entered slave mode (ADR=%d)\n" , slave->addr); |
85 | |
86 | return 0; |
87 | } |
88 | |
89 | static int at91_unreg_slave(struct i2c_client *slave) |
90 | { |
91 | struct at91_twi_dev *dev = i2c_get_adapdata(adap: slave->adapter); |
92 | |
93 | WARN_ON(!dev->slave); |
94 | |
95 | dev_info(dev->dev, "leaving slave mode\n" ); |
96 | |
97 | dev->slave = NULL; |
98 | dev->smr = 0; |
99 | |
100 | at91_init_twi_bus(dev); |
101 | |
102 | pm_runtime_put(dev: dev->dev); |
103 | |
104 | return 0; |
105 | } |
106 | |
107 | static u32 at91_twi_func(struct i2c_adapter *adapter) |
108 | { |
109 | return I2C_FUNC_SLAVE | I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL |
110 | | I2C_FUNC_SMBUS_READ_BLOCK_DATA; |
111 | } |
112 | |
113 | static const struct i2c_algorithm at91_twi_algorithm_slave = { |
114 | .reg_slave = at91_reg_slave, |
115 | .unreg_slave = at91_unreg_slave, |
116 | .functionality = at91_twi_func, |
117 | }; |
118 | |
119 | int at91_twi_probe_slave(struct platform_device *pdev, |
120 | u32 phy_addr, struct at91_twi_dev *dev) |
121 | { |
122 | int rc; |
123 | |
124 | rc = devm_request_irq(dev: &pdev->dev, irq: dev->irq, handler: atmel_twi_interrupt_slave, |
125 | irqflags: 0, devname: dev_name(dev: dev->dev), dev_id: dev); |
126 | if (rc) { |
127 | dev_err(dev->dev, "Cannot get irq %d: %d\n" , dev->irq, rc); |
128 | return rc; |
129 | } |
130 | |
131 | dev->adapter.algo = &at91_twi_algorithm_slave; |
132 | |
133 | return 0; |
134 | } |
135 | |
136 | void at91_init_twi_bus_slave(struct at91_twi_dev *dev) |
137 | { |
138 | at91_twi_write(dev, AT91_TWI_CR, AT91_TWI_MSDIS); |
139 | if (dev->slave_detected && dev->smr) { |
140 | at91_twi_write(dev, AT91_TWI_SMR, val: dev->smr); |
141 | at91_twi_write(dev, AT91_TWI_CR, AT91_TWI_SVEN); |
142 | } |
143 | } |
144 | |