diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2020-08-18 11:23:19 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2020-08-18 11:23:19 +0200 |
commit | eafcd796d24f77a87e525c4690c1f2cff3880f7b (patch) | |
tree | 03d4e77f0bb76f6c0d83bca220f0c4db2bd81154 /drivers | |
parent | cdf3e7ed0d6624108a2a4722f721032d576e5516 (diff) | |
parent | ef99c1e7177148fb1383d3061607b332db75453d (diff) | |
download | barebox-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.c | 135 | ||||
-rw-r--r-- | drivers/i2c/i2c.c | 73 | ||||
-rw-r--r-- | drivers/mfd/Kconfig | 10 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mfd/atmel-flexcom.c | 73 |
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); |