summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAhmad Fatoum <a.fatoum@pengutronix.de>2020-07-21 08:14:57 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2020-08-03 23:33:18 +0200
commit40bd23dc3db4f5a6f35e22532b39a7aadbe92fa7 (patch)
tree236ae02a220cf6836b074c3a62bb5d8c120ebdee
parent2edf0ad33085fbb468ccea35551197834994211b (diff)
downloadbarebox-40bd23dc3db4f5a6f35e22532b39a7aadbe92fa7.tar.gz
barebox-40bd23dc3db4f5a6f35e22532b39a7aadbe92fa7.tar.xz
i2c: at91: extend for sama5d2 support
Port over the Linux v5.8-rc4 bits to support i2c on the sama5d2. This has been tested by reading the i2c EEPROM on the sama5d27-som1-ek. Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
-rw-r--r--drivers/i2c/busses/i2c-at91.c135
1 files changed, 116 insertions, 19 deletions
diff --git a/drivers/i2c/busses/i2c-at91.c b/drivers/i2c/busses/i2c-at91.c
index 76bb51bf30..70e87c1b2c 100644
--- a/drivers/i2c/busses/i2c-at91.c
+++ b/drivers/i2c/busses/i2c-at91.c
@@ -48,6 +48,8 @@
#define AT91_TWI_IADR 0x000c /* Internal Address Register */
#define AT91_TWI_CWGR 0x0010 /* Clock Waveform Generator Reg */
+#define AT91_TWI_CWGR_HOLD_MAX 0x1f
+#define AT91_TWI_CWGR_HOLD(x) (((x) & AT91_TWI_CWGR_HOLD_MAX) << 24)
#define AT91_TWI_SR 0x0020 /* Status Register */
#define AT91_TWI_TXCOMP 0x0001 /* Transmission Complete */
@@ -64,14 +66,27 @@
#define AT91_TWI_RHR 0x0030 /* Receive Holding Register */
#define AT91_TWI_THR 0x0034 /* Transmit Holding Register */
+#define AT91_TWI_FILTR 0x0044
+#define AT91_TWI_FILTR_FILT BIT(0)
+#define AT91_TWI_FILTR_PADFEN BIT(1)
+#define AT91_TWI_FILTR_THRES(v) ((v) << 8)
+#define AT91_TWI_FILTR_THRES_MAX 7
+#define AT91_TWI_FILTR_THRES_MASK GENMASK(10, 8)
+
struct at91_twi_pdata {
unsigned clk_max_div;
unsigned clk_offset;
bool has_unre_flag;
+ bool has_alt_cmd;
+ bool has_hold_field;
+ bool has_dig_filtr;
+ bool has_adv_dig_filtr;
+ bool has_ana_filtr;
+ bool has_clear_cmd;
};
struct at91_twi_dev {
- struct device *dev;
+ struct device_d *dev;
void __iomem *base;
struct clk *clk;
u8 *buf;
@@ -82,6 +97,10 @@ struct at91_twi_dev {
struct i2c_adapter adapter;
unsigned twi_cwgr_reg;
struct at91_twi_pdata *pdata;
+ u32 filter_width;
+
+ bool enable_dig_filt;
+ bool enable_ana_filt;
};
#define to_at91_twi_dev(a) container_of(a, struct at91_twi_dev, adapter)
@@ -104,11 +123,31 @@ static void at91_disable_twi_interrupts(struct at91_twi_dev *dev)
static void at91_init_twi_bus(struct at91_twi_dev *dev)
{
+ struct at91_twi_pdata *pdata = dev->pdata;
+ u32 filtr = 0;
+
at91_disable_twi_interrupts(dev);
at91_twi_write(dev, AT91_TWI_CR, AT91_TWI_SWRST);
at91_twi_write(dev, AT91_TWI_CR, AT91_TWI_MSEN);
at91_twi_write(dev, AT91_TWI_CR, AT91_TWI_SVDIS);
at91_twi_write(dev, AT91_TWI_CWGR, dev->twi_cwgr_reg);
+
+ /* enable digital filter */
+ if (pdata->has_dig_filtr && dev->enable_dig_filt)
+ filtr |= AT91_TWI_FILTR_FILT;
+
+ /* enable advanced digital filter */
+ if (pdata->has_adv_dig_filtr && dev->enable_dig_filt)
+ filtr |= AT91_TWI_FILTR_FILT |
+ (AT91_TWI_FILTR_THRES(dev->filter_width) &
+ AT91_TWI_FILTR_THRES_MASK);
+
+ /* enable analog filter */
+ if (pdata->has_ana_filtr && dev->enable_ana_filt)
+ filtr |= AT91_TWI_FILTR_PADFEN;
+
+ if (filtr)
+ at91_twi_write(dev, AT91_TWI_FILTR, filtr);
}
/*
@@ -117,10 +156,13 @@ static void at91_init_twi_bus(struct at91_twi_dev *dev)
*/
static void at91_calc_twi_clock(struct at91_twi_dev *dev, int twi_clk)
{
- int ckdiv, cdiv, div;
+ int ckdiv, cdiv, div, hold = 0, filter_width = 0;
struct at91_twi_pdata *pdata = dev->pdata;
int offset = pdata->clk_offset;
int max_ckdiv = pdata->clk_max_div;
+ struct i2c_timings timings, *t = &timings;
+
+ i2c_parse_fw_timings(dev->dev, t, true);
div = max(0, (int)DIV_ROUND_UP(clk_get_rate(dev->clk),
2 * twi_clk) - offset);
@@ -128,14 +170,54 @@ static void at91_calc_twi_clock(struct at91_twi_dev *dev, int twi_clk)
cdiv = div >> ckdiv;
if (ckdiv > max_ckdiv) {
- dev_warn(&dev->adapter.dev, "%d exceeds ckdiv max value which is %d.\n",
+ dev_warn(dev->dev, "%d exceeds ckdiv max value which is %d.\n",
ckdiv, max_ckdiv);
ckdiv = max_ckdiv;
cdiv = 255;
}
- dev->twi_cwgr_reg = (ckdiv << 16) | (cdiv << 8) | cdiv;
- dev_dbg(&dev->adapter.dev, "cdiv %d ckdiv %d\n", cdiv, ckdiv);
+ if (pdata->has_hold_field) {
+ /*
+ * hold time = HOLD + 3 x T_peripheral_clock
+ * Use clk rate in kHz to prevent overflows when computing
+ * hold.
+ */
+ hold = DIV_ROUND_UP(t->sda_hold_ns
+ * (clk_get_rate(dev->clk) / 1000), 1000000);
+ hold -= 3;
+ if (hold < 0)
+ hold = 0;
+ if (hold > AT91_TWI_CWGR_HOLD_MAX) {
+ dev_warn(dev->dev,
+ "HOLD field set to its maximum value (%d instead of %d)\n",
+ AT91_TWI_CWGR_HOLD_MAX, hold);
+ hold = AT91_TWI_CWGR_HOLD_MAX;
+ }
+ }
+
+ if (pdata->has_adv_dig_filtr) {
+ /*
+ * filter width = 0 to AT91_TWI_FILTR_THRES_MAX
+ * peripheral clocks
+ */
+ filter_width = DIV_ROUND_UP(t->digital_filter_width_ns
+ * (clk_get_rate(dev->clk) / 1000), 1000000);
+ if (filter_width > AT91_TWI_FILTR_THRES_MAX) {
+ dev_warn(dev->dev,
+ "Filter threshold set to its maximum value (%d instead of %d)\n",
+ AT91_TWI_FILTR_THRES_MAX, filter_width);
+ filter_width = AT91_TWI_FILTR_THRES_MAX;
+ }
+ }
+
+ dev->twi_cwgr_reg = (ckdiv << 16) | (cdiv << 8) | cdiv
+ | AT91_TWI_CWGR_HOLD(hold);
+
+ dev->filter_width = filter_width;
+
+ dev_dbg(dev->dev, "cdiv %d ckdiv %d hold %d (%d ns), filter_width %d (%d ns)\n",
+ cdiv, ckdiv, hold, t->sda_hold_ns, filter_width,
+ t->digital_filter_width_ns);
}
static void at91_twi_write_next_byte(struct at91_twi_dev *dev)
@@ -149,7 +231,7 @@ static void at91_twi_write_next_byte(struct at91_twi_dev *dev)
if (--dev->buf_len == 0)
at91_twi_write(dev, AT91_TWI_CR, AT91_TWI_STOP);
- dev_dbg(&dev->adapter.dev, "wrote 0x%x, to go %d\n", *dev->buf, dev->buf_len);
+ dev_dbg(dev->dev, "wrote 0x%x, to go %d\n", *dev->buf, dev->buf_len);
++dev->buf;
}
@@ -166,7 +248,7 @@ static void at91_twi_read_next_byte(struct at91_twi_dev *dev)
if (dev->buf_len == 1)
at91_twi_write(dev, AT91_TWI_CR, AT91_TWI_STOP);
- dev_dbg(&dev->adapter.dev, "read 0x%x, to go %d\n", *dev->buf, dev->buf_len);
+ dev_dbg(dev->dev, "read 0x%x, to go %d\n", *dev->buf, dev->buf_len);
++dev->buf;
}
@@ -183,7 +265,7 @@ static int at91_twi_wait_completion(struct at91_twi_dev *dev)
if (!(status & irqstatus)) {
if (is_timeout(start, AT91_I2C_TIMEOUT)) {
- dev_warn(&dev->adapter.dev, "timeout waiting for bus ready\n");
+ dev_warn(dev->dev, "timeout waiting for bus ready\n");
return -ETIMEDOUT;
} else {
continue;
@@ -195,7 +277,7 @@ static int at91_twi_wait_completion(struct at91_twi_dev *dev)
else if (irqstatus & AT91_TWI_TXRDY)
at91_twi_write_next_byte(dev);
else
- dev_warn(&dev->adapter.dev, "neither rx and tx are ready\n");
+ dev_warn(dev->dev, "neither rx and tx are ready\n");
dev->transfer_status |= status;
@@ -211,7 +293,7 @@ static int at91_do_twi_transfer(struct at91_twi_dev *dev)
int ret;
bool has_unre_flag = dev->pdata->has_unre_flag;
- dev_dbg(&dev->adapter.dev, "transfer: %s %d bytes.\n",
+ dev_dbg(dev->dev, "transfer: %s %d bytes.\n",
(dev->msg->flags & I2C_M_RD) ? "read" : "write", dev->buf_len);
dev->transfer_status = 0;
@@ -223,7 +305,7 @@ static int at91_do_twi_transfer(struct at91_twi_dev *dev)
unsigned start_flags = AT91_TWI_START;
if (at91_twi_read(dev, AT91_TWI_SR) & AT91_TWI_RXRDY) {
- dev_err(&dev->adapter.dev, "RXRDY still set!");
+ dev_err(dev->dev, "RXRDY still set!");
at91_twi_read(dev, AT91_TWI_RHR);
}
@@ -243,27 +325,27 @@ static int at91_do_twi_transfer(struct at91_twi_dev *dev)
ret = at91_twi_wait_completion(dev);
if (ret < 0) {
- dev_err(&dev->adapter.dev, "controller timed out\n");
+ dev_err(dev->dev, "controller timed out\n");
at91_init_twi_bus(dev);
ret = -ETIMEDOUT;
goto error;
}
if (dev->transfer_status & AT91_TWI_NACK) {
- dev_dbg(&dev->adapter.dev, "received nack\n");
+ dev_dbg(dev->dev, "received nack\n");
ret = -EREMOTEIO;
goto error;
}
if (dev->transfer_status & AT91_TWI_OVRE) {
- dev_err(&dev->adapter.dev, "overrun while reading\n");
+ dev_err(dev->dev, "overrun while reading\n");
ret = -EIO;
goto error;
}
if (has_unre_flag && dev->transfer_status & AT91_TWI_UNRE) {
- dev_err(&dev->adapter.dev, "underrun while writing\n");
+ dev_err(dev->dev, "underrun while writing\n");
ret = -EIO;
goto error;
}
- dev_dbg(&dev->adapter.dev, "transfer complete\n");
+ dev_dbg(dev->dev, "transfer complete\n");
return 0;
@@ -285,7 +367,7 @@ static int at91_twi_xfer(struct i2c_adapter *adap, struct i2c_msg *msg, int num)
* repeated start via it's internal address feature.
*/
if (num > 2) {
- dev_err(&dev->adapter.dev,
+ dev_err(dev->dev,
"cannot handle more than two concatenated messages.\n");
return 0;
} else if (num == 2) {
@@ -293,11 +375,11 @@ static int at91_twi_xfer(struct i2c_adapter *adap, struct i2c_msg *msg, int num)
int i;
if (msg->flags & I2C_M_RD) {
- dev_err(&dev->adapter.dev, "first transfer must be write.\n");
+ dev_err(dev->dev, "first transfer must be write.\n");
return -EINVAL;
}
if (msg->len > 3) {
- dev_err(&dev->adapter.dev, "first message size must be <= 3.\n");
+ dev_err(dev->dev, "first message size must be <= 3.\n");
return -EINVAL;
}
@@ -360,6 +442,17 @@ static struct at91_twi_pdata at91sam9x5_config = {
.has_unre_flag = false,
};
+static struct at91_twi_pdata sama5d2_config = {
+ .clk_max_div = 7,
+ .clk_offset = 3,
+ .has_unre_flag = true,
+ .has_alt_cmd = true,
+ .has_hold_field = true,
+ .has_dig_filtr = true,
+ .has_adv_dig_filtr = true,
+ .has_ana_filtr = true,
+};
+
static struct platform_device_id at91_twi_devtypes[] = {
{
.name = "at91rm9200-i2c",
@@ -404,6 +497,9 @@ static struct of_device_id at91_twi_dt_ids[] = {
.compatible = "atmel,at91sam9x5-i2c",
.data = &at91sam9x5_config,
}, {
+ .compatible = "atmel,sama5d2-i2c",
+ .data = &sama5d2_config,
+ }, {
/* sentinel */
}
};
@@ -417,6 +513,7 @@ static int at91_twi_probe(struct device_d *dev)
u32 bus_clk_rate;
i2c_at91 = xzalloc(sizeof(struct at91_twi_dev));
+ i2c_at91->dev = dev;
rc = dev_get_drvdata(dev, (const void **)&i2c_data);
if (rc < 0) {