summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2020-08-18 11:23:19 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2020-08-18 11:23:19 +0200
commiteafcd796d24f77a87e525c4690c1f2cff3880f7b (patch)
tree03d4e77f0bb76f6c0d83bca220f0c4db2bd81154 /drivers
parentcdf3e7ed0d6624108a2a4722f721032d576e5516 (diff)
parentef99c1e7177148fb1383d3061607b332db75453d (diff)
downloadbarebox-eafcd796d24f77a87e525c4690c1f2cff3880f7b.tar.gz
barebox-eafcd796d24f77a87e525c4690c1f2cff3880f7b.tar.xz
Merge branch 'for-next/at91' into master
Diffstat (limited to 'drivers')
-rw-r--r--drivers/i2c/busses/i2c-at91.c135
-rw-r--r--drivers/i2c/i2c.c73
-rw-r--r--drivers/mfd/Kconfig10
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/atmel-flexcom.c73
5 files changed, 234 insertions, 58 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) {
diff --git a/drivers/i2c/i2c.c b/drivers/i2c/i2c.c
index 2fed624d69..57d8c7017f 100644
--- a/drivers/i2c/i2c.c
+++ b/drivers/i2c/i2c.c
@@ -569,6 +569,18 @@ struct i2c_client *of_find_i2c_device_by_node(struct device_node *node)
return to_i2c_client(dev);
}
+static void i2c_parse_timing(struct device_d *dev, char *prop_name, u32 *cur_val_p,
+ u32 def_val, bool use_def)
+{
+ int ret;
+
+ ret = of_property_read_u32(dev->device_node, prop_name, cur_val_p);
+ if (ret && use_def)
+ *cur_val_p = def_val;
+
+ dev_dbg(dev, "%s: %u\n", prop_name, *cur_val_p);
+}
+
/**
* i2c_parse_fw_timings - get I2C related timing parameters from firmware
* @dev: The device to scan for I2C timing properties
@@ -587,45 +599,28 @@ struct i2c_client *of_find_i2c_device_by_node(struct device_node *node)
void i2c_parse_fw_timings(struct device_d *dev, struct i2c_timings *t, bool use_defaults)
{
- int ret;
-
- memset(t, 0, sizeof(*t));
-
- ret = of_property_read_u32(dev->device_node, "clock-frequency",
- &t->bus_freq_hz);
- if (ret && use_defaults)
- t->bus_freq_hz = 100000;
-
- ret = of_property_read_u32(dev->device_node, "i2c-scl-rising-time-ns",
- &t->scl_rise_ns);
- if (ret && use_defaults) {
- if (t->bus_freq_hz <= 100000)
- t->scl_rise_ns = 1000;
- else if (t->bus_freq_hz <= 400000)
- t->scl_rise_ns = 300;
- else
- t->scl_rise_ns = 120;
- }
-
- ret = of_property_read_u32(dev->device_node, "i2c-scl-falling-time-ns",
- &t->scl_fall_ns);
- if (ret && use_defaults) {
- if (t->bus_freq_hz <= 400000)
- t->scl_fall_ns = 300;
- else
- t->scl_fall_ns = 120;
- }
-
- of_property_read_u32(dev->device_node, "i2c-scl-internal-delay-ns",
- &t->scl_int_delay_ns);
-
- ret = of_property_read_u32(dev->device_node, "i2c-sda-falling-time-ns",
- &t->sda_fall_ns);
- if (ret && use_defaults)
- t->sda_fall_ns = t->scl_fall_ns;
-
- of_property_read_u32(dev->device_node, "i2c-sda-hold-time-ns",
- &t->sda_hold_ns);
+ bool u = use_defaults;
+ u32 d;
+
+ i2c_parse_timing(dev, "clock-frequency", &t->bus_freq_hz,
+ I2C_MAX_STANDARD_MODE_FREQ, u);
+
+ d = t->bus_freq_hz <= I2C_MAX_STANDARD_MODE_FREQ ? 1000 :
+ t->bus_freq_hz <= I2C_MAX_FAST_MODE_FREQ ? 300 : 120;
+ i2c_parse_timing(dev, "i2c-scl-rising-time-ns", &t->scl_rise_ns, d, u);
+
+ d = t->bus_freq_hz <= I2C_MAX_FAST_MODE_FREQ ? 300 : 120;
+ i2c_parse_timing(dev, "i2c-scl-falling-time-ns", &t->scl_fall_ns, d, u);
+
+ i2c_parse_timing(dev, "i2c-scl-internal-delay-ns",
+ &t->scl_int_delay_ns, 0, u);
+ i2c_parse_timing(dev, "i2c-sda-falling-time-ns", &t->sda_fall_ns,
+ t->scl_fall_ns, u);
+ i2c_parse_timing(dev, "i2c-sda-hold-time-ns", &t->sda_hold_ns, 0, u);
+ i2c_parse_timing(dev, "i2c-digital-filter-width-ns",
+ &t->digital_filter_width_ns, 0, u);
+ i2c_parse_timing(dev, "i2c-analog-filter-cutoff-frequency",
+ &t->analog_filter_cutoff_freq_hz, 0, u);
}
EXPORT_SYMBOL_GPL(i2c_parse_fw_timings);
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index d03d481898..d7a8949baf 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -91,4 +91,14 @@ config MFD_STM32_TIMERS
Select this to get regmap support for the timer blocks on STM32
MCUs and MPUs.
+config MFD_ATMEL_FLEXCOM
+ tristate "Atmel Flexcom (Flexible Serial Communication Unit)"
+ depends on OFDEVICE
+ help
+ Select this to get support for Atmel Flexcom. This is a wrapper
+ which embeds a SPI controller, a I2C controller and a USART. Only
+ one function can be used at a time. The choice is done at boot time
+ by the probe function of this MFD driver according to a device tree
+ property.
+
endmenu
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index a3b296a803..690e53693e 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -16,3 +16,4 @@ obj-$(CONFIG_MFD_SUPERIO) += superio.o
obj-$(CONFIG_FINTEK_SUPERIO) += fintek-superio.o
obj-$(CONFIG_SMSC_SUPERIO) += smsc-superio.o
obj-$(CONFIG_MFD_STM32_TIMERS) += stm32-timers.o
+obj-$(CONFIG_MFD_ATMEL_FLEXCOM) += atmel-flexcom.o
diff --git a/drivers/mfd/atmel-flexcom.c b/drivers/mfd/atmel-flexcom.c
new file mode 100644
index 0000000000..996d4850ee
--- /dev/null
+++ b/drivers/mfd/atmel-flexcom.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-FileCopyrightText: (C) 2015 Atmel Corporation
+/*
+ * Driver for Atmel Flexcom
+ * Author: Cyrille Pitchen <cyrille.pitchen@atmel.com>
+ */
+
+#include <common.h>
+#include <of.h>
+#include <linux/clk.h>
+#include <dt-bindings/mfd/atmel-flexcom.h>
+
+/* I/O register offsets */
+#define FLEX_MR 0x0 /* Mode Register */
+#define FLEX_VERSION 0xfc /* Version Register */
+
+/* Mode Register bit fields */
+#define FLEX_MR_OPMODE_OFFSET (0) /* Operating Mode */
+#define FLEX_MR_OPMODE_MASK (0x3 << FLEX_MR_OPMODE_OFFSET)
+#define FLEX_MR_OPMODE(opmode) (((opmode) << FLEX_MR_OPMODE_OFFSET) & \
+ FLEX_MR_OPMODE_MASK)
+
+static int atmel_flexcom_probe(struct device_d *dev)
+{
+ struct resource *res;
+ struct clk *clk;
+ u32 opmode;
+ int err;
+
+ err = of_property_read_u32(dev->device_node,
+ "atmel,flexcom-mode", &opmode);
+ if (err)
+ return err;
+
+ if (opmode < ATMEL_FLEXCOM_MODE_USART || opmode > ATMEL_FLEXCOM_MODE_TWI)
+ return -EINVAL;
+
+ res = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(res))
+ return PTR_ERR(res);
+
+ clk = clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ err = clk_enable(clk);
+ if (err)
+ return err;
+
+ /*
+ * Set the Operating Mode in the Mode Register: only the selected device
+ * is clocked. Hence, registers of the other serial devices remain
+ * inaccessible and are read as zero. Also the external I/O lines of the
+ * Flexcom are muxed to reach the selected device.
+ */
+ writel(FLEX_MR_OPMODE(opmode), IOMEM(res->start) + FLEX_MR);
+
+ clk_disable(clk);
+
+ return of_platform_populate(dev->device_node, NULL, dev);
+}
+
+static const struct of_device_id atmel_flexcom_of_match[] = {
+ { .compatible = "atmel,sama5d2-flexcom" },
+ { /* sentinel */ }
+};
+
+static struct driver_d atmel_flexcom_driver = {
+ .probe = atmel_flexcom_probe,
+ .name = "atmel_flexcom",
+ .of_compatible = atmel_flexcom_of_match,
+};
+coredevice_platform_driver(atmel_flexcom_driver);