diff options
| author | Linus Torvalds <[email protected]> | 2018-06-11 07:20:17 -0700 | 
|---|---|---|
| committer | Linus Torvalds <[email protected]> | 2018-06-11 07:20:17 -0700 | 
| commit | 883cad5ba8cc2d9b740b4ad0a8a91063c99c75a3 (patch) | |
| tree | f91886f6747bbcf239c42cd1c8d55f86f546e8e7 /drivers/mfd/stm32-timers.c | |
| parent | 8d08c0554244f95076cfe2a722e5207d974cd92d (diff) | |
| parent | 556c242045f0c1613aac2e64dc5b2ff0e4bc89e1 (diff) | |
Merge tag 'mfd-next-4.18' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd
Pull MFD updates from Lee Jones:
 "New Device Support:
   - Add support for AXP813 ADC to AXP20x
   - Add support for PM8005, PM8998 and PMI8998
  New Functionality:
   - Add support for Battery Power Supply to AXP813
   - Add support for SYSCON to SPARD SC27XX SPI
   - Add support for RTC to ChromeOS Embedded-Controller
  Fix-ups:
   - Remove unused code; exynos{4,5}-pmu, cros_ec, cros_ec_acpi_gpe
   - Remove duplicate error messages (-ENOMEM, etc); htc-i2cpld,
        janz-cmodio, max8997, rc5t583, sm501, smsc-ece1099, abx500-core,
        si476x-i2c, ti_am335x_tscadc, tps65090, tps6586x, tps65910,
        tps80031, twl6030-irq, viperboard
   - Succinctly use ptr to struct in sizeof(); rc5t583, abx500-core,
        sm501, smsc-ece1099
   - Simplify syntax for NULL ptr checking; abx500-core, sm501
   - No not unnecessarily initialise variables; tps65910, tps65910
   - Reorganise and simplify driver data; omap-usb-tll
   - Move to SPDX license statement; tps68470
   - Probe ADCs via DT; axp20x
   - Use new GPIOD API; arizona-core
   - Constify things; axp20x
   - Reduce code-size (use MACROS, etc); axp20x, omap-usb-host
   - Add DT support/docs; motorola-cpcap
   - Remove VLAs; rave-sp
   - Use devm_* managed resources; cros_ec
   - Interrogate HW for firmware version; rave-sp
   - Provide ACPI support for ChromeOS Embedded-Controller
  Bug Fixes:
   - Reorder ordered (enum) device list; tps65218
   - Only accept valid data from the offset; rave-sp
   - Refrain from copying junk from failed SPI read; cros_ec_dev
   - Fix potential memory leaks; pcf50633-core
   - Fix clock initialisation; twl-core
   - Fix build-issue; tps65911
   - Fix off-by-one error; tps65911
   - Fix code ordering issues; intel-lpss
   - Fix COMPILE_TEST related issues; pwm-stm32
   - Fix broken MMC card detection; asic3
   - Fix clocking related issues; intel-lpss-pci"
* tag 'mfd-next-4.18' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd: (84 commits)
  mfd: cros_ec: Remove unused __remove function
  mfd: wm97xx-core: Platform data can be NULL
  mfd: cros_ec_dev: Don't advertise junk features on failure
  mfd: cros_ec: Use devm_kzalloc for private data
  mfd: intel-lpss: Fix Intel Cannon Lake LPSS I2C input clock
  mfd: asic3: Fix broken MMC card detection
  mfd: timberdale: Fix spelling mistake "Uknown" -> "Unknown"
  mfd: omap-usb-host: Use match_string() helper
  mfd: stm32-timers: Fix pwm-stm32 linker issue with COMPILE_TEST
  pwm: stm32: Initialize raw local variables
  mfd: arizona: Update DT doc to support more standard Reset binding
  dt-bindings: mfd: Add bindings for DA9063L
  mfd: intel-lpss: Correct names of RESETS register bits
  mfd: qcom-spmi-pmic: Add support for pm8005, pm8998 and pmi8998
  mfd: intel-lpss: Program REMAP register in PIO mode
  mfd: cros_ec_i2c: Moving the system sleep pm ops to late
  mfd: cros_ec_i2c: Add ACPI module device table
  mfd: cros_ec_dev: Register shutdown function for debugfs
  mfd: cros_ec_dev: Register cros-ec-rtc driver as a subdevice
  mfd: cros_ec: Don't try to grab log when suspended
  ...
Diffstat (limited to 'drivers/mfd/stm32-timers.c')
| -rw-r--r-- | drivers/mfd/stm32-timers.c | 201 | 
1 files changed, 199 insertions, 2 deletions
diff --git a/drivers/mfd/stm32-timers.c b/drivers/mfd/stm32-timers.c index 1d347e5dfa79..efcd4b980c94 100644 --- a/drivers/mfd/stm32-timers.c +++ b/drivers/mfd/stm32-timers.c @@ -4,16 +4,156 @@   * Author: Benjamin Gaignard <[email protected]>   */ +#include <linux/bitfield.h>  #include <linux/mfd/stm32-timers.h>  #include <linux/module.h>  #include <linux/of_platform.h>  #include <linux/reset.h> +#define STM32_TIMERS_MAX_REGISTERS	0x3fc + +/* DIER register DMA enable bits */ +static const u32 stm32_timers_dier_dmaen[STM32_TIMERS_MAX_DMAS] = { +	TIM_DIER_CC1DE, +	TIM_DIER_CC2DE, +	TIM_DIER_CC3DE, +	TIM_DIER_CC4DE, +	TIM_DIER_UIE, +	TIM_DIER_TDE, +	TIM_DIER_COMDE +}; + +static void stm32_timers_dma_done(void *p) +{ +	struct stm32_timers_dma *dma = p; +	struct dma_tx_state state; +	enum dma_status status; + +	status = dmaengine_tx_status(dma->chan, dma->chan->cookie, &state); +	if (status == DMA_COMPLETE) +		complete(&dma->completion); +} + +/** + * stm32_timers_dma_burst_read - Read from timers registers using DMA. + * + * Read from STM32 timers registers using DMA on a single event. + * @dev: reference to stm32_timers MFD device + * @buf: DMA'able destination buffer + * @id: stm32_timers_dmas event identifier (ch[1..4], up, trig or com) + * @reg: registers start offset for DMA to read from (like CCRx for capture) + * @num_reg: number of registers to read upon each DMA request, starting @reg. + * @bursts: number of bursts to read (e.g. like two for pwm period capture) + * @tmo_ms: timeout (milliseconds) + */ +int stm32_timers_dma_burst_read(struct device *dev, u32 *buf, +				enum stm32_timers_dmas id, u32 reg, +				unsigned int num_reg, unsigned int bursts, +				unsigned long tmo_ms) +{ +	struct stm32_timers *ddata = dev_get_drvdata(dev); +	unsigned long timeout = msecs_to_jiffies(tmo_ms); +	struct regmap *regmap = ddata->regmap; +	struct stm32_timers_dma *dma = &ddata->dma; +	size_t len = num_reg * bursts * sizeof(u32); +	struct dma_async_tx_descriptor *desc; +	struct dma_slave_config config; +	dma_cookie_t cookie; +	dma_addr_t dma_buf; +	u32 dbl, dba; +	long err; +	int ret; + +	/* Sanity check */ +	if (id < STM32_TIMERS_DMA_CH1 || id >= STM32_TIMERS_MAX_DMAS) +		return -EINVAL; + +	if (!num_reg || !bursts || reg > STM32_TIMERS_MAX_REGISTERS || +	    (reg + num_reg * sizeof(u32)) > STM32_TIMERS_MAX_REGISTERS) +		return -EINVAL; + +	if (!dma->chans[id]) +		return -ENODEV; +	mutex_lock(&dma->lock); + +	/* Select DMA channel in use */ +	dma->chan = dma->chans[id]; +	dma_buf = dma_map_single(dev, buf, len, DMA_FROM_DEVICE); +	if (dma_mapping_error(dev, dma_buf)) { +		ret = -ENOMEM; +		goto unlock; +	} + +	/* Prepare DMA read from timer registers, using DMA burst mode */ +	memset(&config, 0, sizeof(config)); +	config.src_addr = (dma_addr_t)dma->phys_base + TIM_DMAR; +	config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; +	ret = dmaengine_slave_config(dma->chan, &config); +	if (ret) +		goto unmap; + +	desc = dmaengine_prep_slave_single(dma->chan, dma_buf, len, +					   DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); +	if (!desc) { +		ret = -EBUSY; +		goto unmap; +	} + +	desc->callback = stm32_timers_dma_done; +	desc->callback_param = dma; +	cookie = dmaengine_submit(desc); +	ret = dma_submit_error(cookie); +	if (ret) +		goto dma_term; + +	reinit_completion(&dma->completion); +	dma_async_issue_pending(dma->chan); + +	/* Setup and enable timer DMA burst mode */ +	dbl = FIELD_PREP(TIM_DCR_DBL, bursts - 1); +	dba = FIELD_PREP(TIM_DCR_DBA, reg >> 2); +	ret = regmap_write(regmap, TIM_DCR, dbl | dba); +	if (ret) +		goto dma_term; + +	/* Clear pending flags before enabling DMA request */ +	ret = regmap_write(regmap, TIM_SR, 0); +	if (ret) +		goto dcr_clr; + +	ret = regmap_update_bits(regmap, TIM_DIER, stm32_timers_dier_dmaen[id], +				 stm32_timers_dier_dmaen[id]); +	if (ret) +		goto dcr_clr; + +	err = wait_for_completion_interruptible_timeout(&dma->completion, +							timeout); +	if (err == 0) +		ret = -ETIMEDOUT; +	else if (err < 0) +		ret = err; + +	regmap_update_bits(regmap, TIM_DIER, stm32_timers_dier_dmaen[id], 0); +	regmap_write(regmap, TIM_SR, 0); +dcr_clr: +	regmap_write(regmap, TIM_DCR, 0); +dma_term: +	dmaengine_terminate_all(dma->chan); +unmap: +	dma_unmap_single(dev, dma_buf, len, DMA_FROM_DEVICE); +unlock: +	dma->chan = NULL; +	mutex_unlock(&dma->lock); + +	return ret; +} +EXPORT_SYMBOL_GPL(stm32_timers_dma_burst_read); +  static const struct regmap_config stm32_timers_regmap_cfg = {  	.reg_bits = 32,  	.val_bits = 32,  	.reg_stride = sizeof(u32), -	.max_register = 0x3fc, +	.max_register = STM32_TIMERS_MAX_REGISTERS,  };  static void stm32_timers_get_arr_size(struct stm32_timers *ddata) @@ -27,12 +167,45 @@ static void stm32_timers_get_arr_size(struct stm32_timers *ddata)  	regmap_write(ddata->regmap, TIM_ARR, 0x0);  } +static void stm32_timers_dma_probe(struct device *dev, +				   struct stm32_timers *ddata) +{ +	int i; +	char name[4]; + +	init_completion(&ddata->dma.completion); +	mutex_init(&ddata->dma.lock); + +	/* Optional DMA support: get valid DMA channel(s) or NULL */ +	for (i = STM32_TIMERS_DMA_CH1; i <= STM32_TIMERS_DMA_CH4; i++) { +		snprintf(name, ARRAY_SIZE(name), "ch%1d", i + 1); +		ddata->dma.chans[i] = dma_request_slave_channel(dev, name); +	} +	ddata->dma.chans[STM32_TIMERS_DMA_UP] = +		dma_request_slave_channel(dev, "up"); +	ddata->dma.chans[STM32_TIMERS_DMA_TRIG] = +		dma_request_slave_channel(dev, "trig"); +	ddata->dma.chans[STM32_TIMERS_DMA_COM] = +		dma_request_slave_channel(dev, "com"); +} + +static void stm32_timers_dma_remove(struct device *dev, +				    struct stm32_timers *ddata) +{ +	int i; + +	for (i = STM32_TIMERS_DMA_CH1; i < STM32_TIMERS_MAX_DMAS; i++) +		if (ddata->dma.chans[i]) +			dma_release_channel(ddata->dma.chans[i]); +} +  static int stm32_timers_probe(struct platform_device *pdev)  {  	struct device *dev = &pdev->dev;  	struct stm32_timers *ddata;  	struct resource *res;  	void __iomem *mmio; +	int ret;  	ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);  	if (!ddata) @@ -43,6 +216,9 @@ static int stm32_timers_probe(struct platform_device *pdev)  	if (IS_ERR(mmio))  		return PTR_ERR(mmio); +	/* Timer physical addr for DMA */ +	ddata->dma.phys_base = res->start; +  	ddata->regmap = devm_regmap_init_mmio_clk(dev, "int", mmio,  						  &stm32_timers_regmap_cfg);  	if (IS_ERR(ddata->regmap)) @@ -54,9 +230,29 @@ static int stm32_timers_probe(struct platform_device *pdev)  	stm32_timers_get_arr_size(ddata); +	stm32_timers_dma_probe(dev, ddata); +  	platform_set_drvdata(pdev, ddata); -	return devm_of_platform_populate(&pdev->dev); +	ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); +	if (ret) +		stm32_timers_dma_remove(dev, ddata); + +	return ret; +} + +static int stm32_timers_remove(struct platform_device *pdev) +{ +	struct stm32_timers *ddata = platform_get_drvdata(pdev); + +	/* +	 * Don't use devm_ here: enfore of_platform_depopulate() happens before +	 * DMA are released, to avoid race on DMA. +	 */ +	of_platform_depopulate(&pdev->dev); +	stm32_timers_dma_remove(&pdev->dev, ddata); + +	return 0;  }  static const struct of_device_id stm32_timers_of_match[] = { @@ -67,6 +263,7 @@ MODULE_DEVICE_TABLE(of, stm32_timers_of_match);  static struct platform_driver stm32_timers_driver = {  	.probe = stm32_timers_probe, +	.remove = stm32_timers_remove,  	.driver	= {  		.name = "stm32-timers",  		.of_match_table = stm32_timers_of_match,  |