diff options
Diffstat (limited to 'drivers/spi/spi-axi-spi-engine.c')
| -rw-r--r-- | drivers/spi/spi-axi-spi-engine.c | 68 | 
1 files changed, 53 insertions, 15 deletions
| diff --git a/drivers/spi/spi-axi-spi-engine.c b/drivers/spi/spi-axi-spi-engine.c index e358ac5b4509..447e5a962dee 100644 --- a/drivers/spi/spi-axi-spi-engine.c +++ b/drivers/spi/spi-axi-spi-engine.c @@ -46,6 +46,7 @@  #define SPI_ENGINE_INST_ASSERT			0x1  #define SPI_ENGINE_INST_WRITE			0x2  #define SPI_ENGINE_INST_MISC			0x3 +#define SPI_ENGINE_INST_CS_INV			0x4  #define SPI_ENGINE_CMD_REG_CLK_DIV		0x0  #define SPI_ENGINE_CMD_REG_CONFIG		0x1 @@ -73,6 +74,8 @@  	SPI_ENGINE_CMD(SPI_ENGINE_INST_MISC, SPI_ENGINE_MISC_SLEEP, (delay))  #define SPI_ENGINE_CMD_SYNC(id) \  	SPI_ENGINE_CMD(SPI_ENGINE_INST_MISC, SPI_ENGINE_MISC_SYNC, (id)) +#define SPI_ENGINE_CMD_CS_INV(flags) \ +	SPI_ENGINE_CMD(SPI_ENGINE_INST_CS_INV, 0, (flags))  struct spi_engine_program {  	unsigned int length; @@ -111,6 +114,8 @@ struct spi_engine {  	struct spi_engine_message_state msg_state;  	struct completion msg_complete;  	unsigned int int_enable; +	/* shadows hardware CS inversion flag state */ +	u8 cs_inv;  };  static void spi_engine_program_add_cmd(struct spi_engine_program *p, @@ -164,16 +169,20 @@ static void spi_engine_gen_xfer(struct spi_engine_program *p, bool dry,  }  static void spi_engine_gen_sleep(struct spi_engine_program *p, bool dry, -				 int delay_ns, u32 sclk_hz) +				 int delay_ns, int inst_ns, u32 sclk_hz)  {  	unsigned int t; -	/* negative delay indicates error, e.g. from spi_delay_to_ns() */ -	if (delay_ns <= 0) +	/* +	 * Negative delay indicates error, e.g. from spi_delay_to_ns(). And if +	 * delay is less that the instruction execution time, there is no need +	 * for an extra sleep instruction since the instruction execution time +	 * will already cover the required delay. +	 */ +	if (delay_ns < 0 || delay_ns <= inst_ns)  		return; -	/* rounding down since executing the instruction adds a couple of ticks delay */ -	t = DIV_ROUND_DOWN_ULL((u64)delay_ns * sclk_hz, NSEC_PER_SEC); +	t = DIV_ROUND_UP_ULL((u64)(delay_ns - inst_ns) * sclk_hz, NSEC_PER_SEC);  	while (t) {  		unsigned int n = min(t, 256U); @@ -220,10 +229,16 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,  	struct spi_device *spi = msg->spi;  	struct spi_controller *host = spi->controller;  	struct spi_transfer *xfer; -	int clk_div, new_clk_div; +	int clk_div, new_clk_div, inst_ns;  	bool keep_cs = false;  	u8 bits_per_word = 0; +	/* +	 * Take into account instruction execution time for more accurate sleep +	 * times, especially when the delay is small. +	 */ +	inst_ns = DIV_ROUND_UP(NSEC_PER_SEC, host->max_speed_hz); +  	clk_div = 1;  	spi_engine_program_add_cmd(p, dry, @@ -252,7 +267,7 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,  		spi_engine_gen_xfer(p, dry, xfer);  		spi_engine_gen_sleep(p, dry, spi_delay_to_ns(&xfer->delay, xfer), -				     xfer->effective_speed_hz); +				     inst_ns, xfer->effective_speed_hz);  		if (xfer->cs_change) {  			if (list_is_last(&xfer->transfer_list, &msg->transfers)) { @@ -262,7 +277,7 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,  					spi_engine_gen_cs(p, dry, spi, false);  				spi_engine_gen_sleep(p, dry, spi_delay_to_ns( -					&xfer->cs_change_delay, xfer), +					&xfer->cs_change_delay, xfer), inst_ns,  					xfer->effective_speed_hz);  				if (!list_next_entry(xfer, transfer_list)->cs_off) @@ -530,6 +545,29 @@ static int spi_engine_unoptimize_message(struct spi_message *msg)  	return 0;  } +static int spi_engine_setup(struct spi_device *device) +{ +	struct spi_controller *host = device->controller; +	struct spi_engine *spi_engine = spi_controller_get_devdata(host); + +	if (device->mode & SPI_CS_HIGH) +		spi_engine->cs_inv |= BIT(spi_get_chipselect(device, 0)); +	else +		spi_engine->cs_inv &= ~BIT(spi_get_chipselect(device, 0)); + +	writel_relaxed(SPI_ENGINE_CMD_CS_INV(spi_engine->cs_inv), +		       spi_engine->base + SPI_ENGINE_REG_CMD_FIFO); + +	/* +	 * In addition to setting the flags, we have to do a CS assert command +	 * to make the new setting actually take effect. +	 */ +	writel_relaxed(SPI_ENGINE_CMD_ASSERT(0, 0xff), +		       spi_engine->base + SPI_ENGINE_REG_CMD_FIFO); + +	return 0; +} +  static int spi_engine_transfer_one_message(struct spi_controller *host,  	struct spi_message *msg)  { @@ -653,16 +691,16 @@ static int spi_engine_probe(struct platform_device *pdev)  	host->unoptimize_message = spi_engine_unoptimize_message;  	host->num_chipselect = 8; +	/* Some features depend of the IP core version. */ +	if (ADI_AXI_PCORE_VER_MINOR(version) >= 2) { +		host->mode_bits |= SPI_CS_HIGH; +		host->setup = spi_engine_setup; +	} +  	if (host->max_speed_hz == 0)  		return dev_err_probe(&pdev->dev, -EINVAL, "spi_clk rate is 0"); -	ret = devm_spi_register_controller(&pdev->dev, host); -	if (ret) -		return ret; - -	platform_set_drvdata(pdev, host); - -	return 0; +	return devm_spi_register_controller(&pdev->dev, host);  }  static const struct of_device_id spi_engine_match_table[] = { |