atmel_spi.c

来自「linux 内核源代码」· C语言 代码 · 共 791 行 · 第 1/2 页

C
791
字号
					udelay(1);					cs_activate(as, msg->spi);				}				/*				 * Not done yet. Submit the next transfer.				 *				 * FIXME handle protocol options for xfer				 */				atmel_spi_next_xfer(master, msg);			}		} else {			/*			 * Keep going, we still have data to send in			 * the current transfer.			 */			atmel_spi_next_xfer(master, msg);		}	}	spin_unlock(&as->lock);	return ret;}/* the spi->mode bits understood by this driver: */#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH)static int atmel_spi_setup(struct spi_device *spi){	struct atmel_spi	*as;	u32			scbr, csr;	unsigned int		bits = spi->bits_per_word;	unsigned long		bus_hz, sck_hz;	unsigned int		npcs_pin;	int			ret;	as = spi_master_get_devdata(spi->master);	if (as->stopping)		return -ESHUTDOWN;	if (spi->chip_select > spi->master->num_chipselect) {		dev_dbg(&spi->dev,				"setup: invalid chipselect %u (%u defined)\n",				spi->chip_select, spi->master->num_chipselect);		return -EINVAL;	}	if (bits == 0)		bits = 8;	if (bits < 8 || bits > 16) {		dev_dbg(&spi->dev,				"setup: invalid bits_per_word %u (8 to 16)\n",				bits);		return -EINVAL;	}	if (spi->mode & ~MODEBITS) {		dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n",			spi->mode & ~MODEBITS);		return -EINVAL;	}	/* see notes above re chipselect */	if (cpu_is_at91rm9200()			&& spi->chip_select == 0			&& (spi->mode & SPI_CS_HIGH)) {		dev_dbg(&spi->dev, "setup: can't be active-high\n");		return -EINVAL;	}	/* speed zero convention is used by some upper layers */	bus_hz = clk_get_rate(as->clk);	if (spi->max_speed_hz) {		/* assume div32/fdiv/mbz == 0 */		if (!as->new_1)			bus_hz /= 2;		scbr = ((bus_hz + spi->max_speed_hz - 1)			/ spi->max_speed_hz);		if (scbr >= (1 << SPI_SCBR_SIZE)) {			dev_dbg(&spi->dev,				"setup: %d Hz too slow, scbr %u; min %ld Hz\n",				spi->max_speed_hz, scbr, bus_hz/255);			return -EINVAL;		}	} else		scbr = 0xff;	sck_hz = bus_hz / scbr;	csr = SPI_BF(SCBR, scbr) | SPI_BF(BITS, bits - 8);	if (spi->mode & SPI_CPOL)		csr |= SPI_BIT(CPOL);	if (!(spi->mode & SPI_CPHA))		csr |= SPI_BIT(NCPHA);	/* TODO: DLYBS and DLYBCT */	csr |= SPI_BF(DLYBS, 10);	csr |= SPI_BF(DLYBCT, 10);	/* chipselect must have been muxed as GPIO (e.g. in board setup) */	npcs_pin = (unsigned int)spi->controller_data;	if (!spi->controller_state) {		ret = gpio_request(npcs_pin, spi->dev.bus_id);		if (ret)			return ret;		spi->controller_state = (void *)npcs_pin;		gpio_direction_output(npcs_pin, !(spi->mode & SPI_CS_HIGH));	} else {		unsigned long		flags;		spin_lock_irqsave(&as->lock, flags);		if (as->stay == spi)			as->stay = NULL;		cs_deactivate(as, spi);		spin_unlock_irqrestore(&as->lock, flags);	}	dev_dbg(&spi->dev,		"setup: %lu Hz bpw %u mode 0x%x -> csr%d %08x\n",		sck_hz, bits, spi->mode, spi->chip_select, csr);	spi_writel(as, CSR0 + 4 * spi->chip_select, csr);	return 0;}static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg){	struct atmel_spi	*as;	struct spi_transfer	*xfer;	unsigned long		flags;	struct device		*controller = spi->master->dev.parent;	as = spi_master_get_devdata(spi->master);	dev_dbg(controller, "new message %p submitted for %s\n",			msg, spi->dev.bus_id);	if (unlikely(list_empty(&msg->transfers)			|| !spi->max_speed_hz))		return -EINVAL;	if (as->stopping)		return -ESHUTDOWN;	list_for_each_entry(xfer, &msg->transfers, transfer_list) {		if (!(xfer->tx_buf || xfer->rx_buf)) {			dev_dbg(&spi->dev, "missing rx or tx buf\n");			return -EINVAL;		}		/* FIXME implement these protocol options!! */		if (xfer->bits_per_word || xfer->speed_hz) {			dev_dbg(&spi->dev, "no protocol options yet\n");			return -ENOPROTOOPT;		}		/*		 * DMA map early, for performance (empties dcache ASAP) and		 * better fault reporting.  This is a DMA-only driver.		 *		 * NOTE that if dma_unmap_single() ever starts to do work on		 * platforms supported by this driver, we would need to clean		 * up mappings for previously-mapped transfers.		 */		if (!msg->is_dma_mapped) {			if (atmel_spi_dma_map_xfer(as, xfer) < 0)				return -ENOMEM;		}	}#ifdef VERBOSE	list_for_each_entry(xfer, &msg->transfers, transfer_list) {		dev_dbg(controller,			"  xfer %p: len %u tx %p/%08x rx %p/%08x\n",			xfer, xfer->len,			xfer->tx_buf, xfer->tx_dma,			xfer->rx_buf, xfer->rx_dma);	}#endif	msg->status = -EINPROGRESS;	msg->actual_length = 0;	spin_lock_irqsave(&as->lock, flags);	list_add_tail(&msg->queue, &as->queue);	if (!as->current_transfer)		atmel_spi_next_message(spi->master);	spin_unlock_irqrestore(&as->lock, flags);	return 0;}static void atmel_spi_cleanup(struct spi_device *spi){	struct atmel_spi	*as = spi_master_get_devdata(spi->master);	unsigned		gpio = (unsigned) spi->controller_data;	unsigned long		flags;	if (!spi->controller_state)		return;	spin_lock_irqsave(&as->lock, flags);	if (as->stay == spi) {		as->stay = NULL;		cs_deactivate(as, spi);	}	spin_unlock_irqrestore(&as->lock, flags);	gpio_free(gpio);}/*-------------------------------------------------------------------------*/static int __init atmel_spi_probe(struct platform_device *pdev){	struct resource		*regs;	int			irq;	struct clk		*clk;	int			ret;	struct spi_master	*master;	struct atmel_spi	*as;	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);	if (!regs)		return -ENXIO;	irq = platform_get_irq(pdev, 0);	if (irq < 0)		return irq;	clk = clk_get(&pdev->dev, "spi_clk");	if (IS_ERR(clk))		return PTR_ERR(clk);	/* setup spi core then atmel-specific driver state */	ret = -ENOMEM;	master = spi_alloc_master(&pdev->dev, sizeof *as);	if (!master)		goto out_free;	master->bus_num = pdev->id;	master->num_chipselect = 4;	master->setup = atmel_spi_setup;	master->transfer = atmel_spi_transfer;	master->cleanup = atmel_spi_cleanup;	platform_set_drvdata(pdev, master);	as = spi_master_get_devdata(master);	/*	 * Scratch buffer is used for throwaway rx and tx data.	 * It's coherent to minimize dcache pollution.	 */	as->buffer = dma_alloc_coherent(&pdev->dev, BUFFER_SIZE,					&as->buffer_dma, GFP_KERNEL);	if (!as->buffer)		goto out_free;	spin_lock_init(&as->lock);	INIT_LIST_HEAD(&as->queue);	as->pdev = pdev;	as->regs = ioremap(regs->start, (regs->end - regs->start) + 1);	if (!as->regs)		goto out_free_buffer;	as->irq = irq;	as->clk = clk;	if (!cpu_is_at91rm9200())		as->new_1 = 1;	ret = request_irq(irq, atmel_spi_interrupt, 0,			pdev->dev.bus_id, master);	if (ret)		goto out_unmap_regs;	/* Initialize the hardware */	clk_enable(clk);	spi_writel(as, CR, SPI_BIT(SWRST));	spi_writel(as, MR, SPI_BIT(MSTR) | SPI_BIT(MODFDIS));	spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));	spi_writel(as, CR, SPI_BIT(SPIEN));	/* go! */	dev_info(&pdev->dev, "Atmel SPI Controller at 0x%08lx (irq %d)\n",			(unsigned long)regs->start, irq);	ret = spi_register_master(master);	if (ret)		goto out_reset_hw;	return 0;out_reset_hw:	spi_writel(as, CR, SPI_BIT(SWRST));	clk_disable(clk);	free_irq(irq, master);out_unmap_regs:	iounmap(as->regs);out_free_buffer:	dma_free_coherent(&pdev->dev, BUFFER_SIZE, as->buffer,			as->buffer_dma);out_free:	clk_put(clk);	spi_master_put(master);	return ret;}static int __exit atmel_spi_remove(struct platform_device *pdev){	struct spi_master	*master = platform_get_drvdata(pdev);	struct atmel_spi	*as = spi_master_get_devdata(master);	struct spi_message	*msg;	/* reset the hardware and block queue progress */	spin_lock_irq(&as->lock);	as->stopping = 1;	spi_writel(as, CR, SPI_BIT(SWRST));	spi_readl(as, SR);	spin_unlock_irq(&as->lock);	/* Terminate remaining queued transfers */	list_for_each_entry(msg, &as->queue, queue) {		/* REVISIT unmapping the dma is a NOP on ARM and AVR32		 * but we shouldn't depend on that...		 */		msg->status = -ESHUTDOWN;		msg->complete(msg->context);	}	dma_free_coherent(&pdev->dev, BUFFER_SIZE, as->buffer,			as->buffer_dma);	clk_disable(as->clk);	clk_put(as->clk);	free_irq(as->irq, master);	iounmap(as->regs);	spi_unregister_master(master);	return 0;}#ifdef	CONFIG_PMstatic int atmel_spi_suspend(struct platform_device *pdev, pm_message_t mesg){	struct spi_master	*master = platform_get_drvdata(pdev);	struct atmel_spi	*as = spi_master_get_devdata(master);	clk_disable(as->clk);	return 0;}static int atmel_spi_resume(struct platform_device *pdev){	struct spi_master	*master = platform_get_drvdata(pdev);	struct atmel_spi	*as = spi_master_get_devdata(master);	clk_enable(as->clk);	return 0;}#else#define	atmel_spi_suspend	NULL#define	atmel_spi_resume	NULL#endifstatic struct platform_driver atmel_spi_driver = {	.driver		= {		.name	= "atmel_spi",		.owner	= THIS_MODULE,	},	.suspend	= atmel_spi_suspend,	.resume		= atmel_spi_resume,	.remove		= __exit_p(atmel_spi_remove),};static int __init atmel_spi_init(void){	return platform_driver_probe(&atmel_spi_driver, atmel_spi_probe);}module_init(atmel_spi_init);static void __exit atmel_spi_exit(void){	platform_driver_unregister(&atmel_spi_driver);}module_exit(atmel_spi_exit);MODULE_DESCRIPTION("Atmel AT32/AT91 SPI Controller driver");MODULE_AUTHOR("Haavard Skinnemoen <hskinnemoen@atmel.com>");MODULE_LICENSE("GPL");

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?