static void pch_spi_handle_dma()

in spi-topcliff-pch.c [917:1123]


static void pch_spi_handle_dma(struct pch_spi_data *data, int *bpw)
{
	const u8 *tx_buf;
	const u16 *tx_sbuf;
	u8 *tx_dma_buf;
	u16 *tx_dma_sbuf;
	struct scatterlist *sg;
	struct dma_async_tx_descriptor *desc_tx;
	struct dma_async_tx_descriptor *desc_rx;
	int num;
	int i;
	int size;
	int rem;
	int head;
	unsigned long flags;
	struct pch_spi_dma_ctrl *dma;

	dma = &data->dma;

	/* set baud rate if needed */
	if (data->cur_trans->speed_hz) {
		dev_dbg(&data->master->dev, "%s:setting baud rate\n", __func__);
		spin_lock_irqsave(&data->lock, flags);
		pch_spi_set_baud_rate(data->master, data->cur_trans->speed_hz);
		spin_unlock_irqrestore(&data->lock, flags);
	}

	/* set bits per word if needed */
	if (data->cur_trans->bits_per_word &&
	    (data->current_msg->spi->bits_per_word !=
	     data->cur_trans->bits_per_word)) {
		dev_dbg(&data->master->dev, "%s:set bits per word\n", __func__);
		spin_lock_irqsave(&data->lock, flags);
		pch_spi_set_bits_per_word(data->master,
					  data->cur_trans->bits_per_word);
		spin_unlock_irqrestore(&data->lock, flags);
		*bpw = data->cur_trans->bits_per_word;
	} else {
		*bpw = data->current_msg->spi->bits_per_word;
	}
	data->bpw_len = data->cur_trans->len / (*bpw / 8);

	if (data->bpw_len > PCH_BUF_SIZE) {
		data->bpw_len = PCH_BUF_SIZE;
		data->cur_trans->len -= PCH_BUF_SIZE;
	}

	/* copy Tx Data */
	if (data->cur_trans->tx_buf != NULL) {
		if (*bpw == 8) {
			tx_buf = data->cur_trans->tx_buf;
			tx_dma_buf = dma->tx_buf_virt;
			for (i = 0; i < data->bpw_len; i++)
				*tx_dma_buf++ = *tx_buf++;
		} else {
			tx_sbuf = data->cur_trans->tx_buf;
			tx_dma_sbuf = dma->tx_buf_virt;
			for (i = 0; i < data->bpw_len; i++)
				*tx_dma_sbuf++ = *tx_sbuf++;
		}
	}

	/* Calculate Rx parameter for DMA transmitting */
	if (data->bpw_len > PCH_DMA_TRANS_SIZE) {
		if (data->bpw_len % PCH_DMA_TRANS_SIZE) {
			num = data->bpw_len / PCH_DMA_TRANS_SIZE + 1;
			rem = data->bpw_len % PCH_DMA_TRANS_SIZE;
		} else {
			num = data->bpw_len / PCH_DMA_TRANS_SIZE;
			rem = PCH_DMA_TRANS_SIZE;
		}
		size = PCH_DMA_TRANS_SIZE;
	} else {
		num = 1;
		size = data->bpw_len;
		rem = data->bpw_len;
	}
	dev_dbg(&data->master->dev, "%s num=%d size=%d rem=%d\n",
		__func__, num, size, rem);
	spin_lock_irqsave(&data->lock, flags);

	/* set receive fifo threshold and transmit fifo threshold */
	pch_spi_setclr_reg(data->master, PCH_SPCR,
			   ((size - 1) << SPCR_RFIC_FIELD) |
			   (PCH_TX_THOLD << SPCR_TFIC_FIELD),
			   MASK_RFIC_SPCR_BITS | MASK_TFIC_SPCR_BITS);

	spin_unlock_irqrestore(&data->lock, flags);

	/* RX */
	dma->sg_rx_p = kmalloc_array(num, sizeof(*dma->sg_rx_p), GFP_ATOMIC);
	if (!dma->sg_rx_p)
		return;

	sg_init_table(dma->sg_rx_p, num); /* Initialize SG table */
	/* offset, length setting */
	sg = dma->sg_rx_p;
	for (i = 0; i < num; i++, sg++) {
		if (i == (num - 2)) {
			sg->offset = size * i;
			sg->offset = sg->offset * (*bpw / 8);
			sg_set_page(sg, virt_to_page(dma->rx_buf_virt), rem,
				    sg->offset);
			sg_dma_len(sg) = rem;
		} else if (i == (num - 1)) {
			sg->offset = size * (i - 1) + rem;
			sg->offset = sg->offset * (*bpw / 8);
			sg_set_page(sg, virt_to_page(dma->rx_buf_virt), size,
				    sg->offset);
			sg_dma_len(sg) = size;
		} else {
			sg->offset = size * i;
			sg->offset = sg->offset * (*bpw / 8);
			sg_set_page(sg, virt_to_page(dma->rx_buf_virt), size,
				    sg->offset);
			sg_dma_len(sg) = size;
		}
		sg_dma_address(sg) = dma->rx_buf_dma + sg->offset;
	}
	sg = dma->sg_rx_p;
	desc_rx = dmaengine_prep_slave_sg(dma->chan_rx, sg,
					num, DMA_DEV_TO_MEM,
					DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
	if (!desc_rx) {
		dev_err(&data->master->dev,
			"%s:dmaengine_prep_slave_sg Failed\n", __func__);
		return;
	}
	dma_sync_sg_for_device(&data->master->dev, sg, num, DMA_FROM_DEVICE);
	desc_rx->callback = pch_dma_rx_complete;
	desc_rx->callback_param = data;
	dma->nent = num;
	dma->desc_rx = desc_rx;

	/* Calculate Tx parameter for DMA transmitting */
	if (data->bpw_len > PCH_MAX_FIFO_DEPTH) {
		head = PCH_MAX_FIFO_DEPTH - PCH_DMA_TRANS_SIZE;
		if (data->bpw_len % PCH_DMA_TRANS_SIZE > 4) {
			num = data->bpw_len / PCH_DMA_TRANS_SIZE + 1;
			rem = data->bpw_len % PCH_DMA_TRANS_SIZE - head;
		} else {
			num = data->bpw_len / PCH_DMA_TRANS_SIZE;
			rem = data->bpw_len % PCH_DMA_TRANS_SIZE +
			      PCH_DMA_TRANS_SIZE - head;
		}
		size = PCH_DMA_TRANS_SIZE;
	} else {
		num = 1;
		size = data->bpw_len;
		rem = data->bpw_len;
		head = 0;
	}

	dma->sg_tx_p = kmalloc_array(num, sizeof(*dma->sg_tx_p), GFP_ATOMIC);
	if (!dma->sg_tx_p)
		return;

	sg_init_table(dma->sg_tx_p, num); /* Initialize SG table */
	/* offset, length setting */
	sg = dma->sg_tx_p;
	for (i = 0; i < num; i++, sg++) {
		if (i == 0) {
			sg->offset = 0;
			sg_set_page(sg, virt_to_page(dma->tx_buf_virt), size + head,
				    sg->offset);
			sg_dma_len(sg) = size + head;
		} else if (i == (num - 1)) {
			sg->offset = head + size * i;
			sg->offset = sg->offset * (*bpw / 8);
			sg_set_page(sg, virt_to_page(dma->tx_buf_virt), rem,
				    sg->offset);
			sg_dma_len(sg) = rem;
		} else {
			sg->offset = head + size * i;
			sg->offset = sg->offset * (*bpw / 8);
			sg_set_page(sg, virt_to_page(dma->tx_buf_virt), size,
				    sg->offset);
			sg_dma_len(sg) = size;
		}
		sg_dma_address(sg) = dma->tx_buf_dma + sg->offset;
	}
	sg = dma->sg_tx_p;
	desc_tx = dmaengine_prep_slave_sg(dma->chan_tx,
					sg, num, DMA_MEM_TO_DEV,
					DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
	if (!desc_tx) {
		dev_err(&data->master->dev,
			"%s:dmaengine_prep_slave_sg Failed\n", __func__);
		return;
	}
	dma_sync_sg_for_device(&data->master->dev, sg, num, DMA_TO_DEVICE);
	desc_tx->callback = NULL;
	desc_tx->callback_param = data;
	dma->nent = num;
	dma->desc_tx = desc_tx;

	dev_dbg(&data->master->dev, "%s:Pulling down SSN low - writing 0x2 to SSNXCR\n", __func__);

	spin_lock_irqsave(&data->lock, flags);
	pch_spi_writereg(data->master, PCH_SSNXCR, SSN_LOW);
	desc_rx->tx_submit(desc_rx);
	desc_tx->tx_submit(desc_tx);
	spin_unlock_irqrestore(&data->lock, flags);

	/* reset transfer complete flag */
	data->transfer_complete = false;
}