第 2 章 ARM的PL08X的代码的详细解析 第二部分

目录

2.1. fill_LLIS_for_desc
2.2. pl08x_set_cregs
2.3. pl08x_memrelease
2.4. pl08x_disable_dmac_chan
2.5. pl08x_enable_dmac_chan
2.6. pl08x_dma_busy
2.7. pl08x_wqfunc
2.8. pl08x_amba_driver
2.9. pl08x_make_LLIs
2.10. pl08x_irq
2.11. pl08x_ensure_on
2.12. pl08x_dma_enumerate_channels
2.13. pl08x_probe
2.14. pl08x_init

摘要

2.1. fill_LLIS_for_desc

/*
 * Note that we assume we never have to change the burst sizes
 * Return 0 for error
 */
int fill_LLIS_for_desc(struct pl08x_txd *local_txd, int pl08x_chan_num)
{
	struct pl08x_clientdev_data *client = local_txd->pcd;
	struct pl08x_bus_data *mbus, *sbus;
	int remainder;
	int num_llis = 0;
	union _cctl cctl_parm;
	int max_bytes_per_lli;
	int total_bytes = 0;
	struct _lli *llis_va;
	struct _lli *llis_bus;

	if (!local_txd) {
		dev_err(&pd.dmac->dev, "%s - no descriptor\n", __func__);
		return 0;
	}

	/*
	 * Get some LLIs
	 *  This alloc can wait if the pool is used up so we need to cleanup
	 */
	local_txd->llis_va = dma_pool_alloc(pd.pool, GFP_KERNEL,
					&local_txd->llis_bus);
	if (!local_txd->llis_va) {
		dev_err(&pd.dmac->dev, "%s - no llis\n", __func__);
		return 0;
	}

	pd.pool_ctr++;

	/*
	 * Initialize bus values for this transfer
	 * from the passed optimal values
	 */
	if (!client) {
		dev_err(&pd.dmac->dev, "%s - no client\n", __func__);
		return 0;
	}

	cctl_parm.val = client->cctl_opt;
	local_txd->srcbus.maxwidth =1
		pl08x_decode_widthbits(cctl_parm.bits.swidth);

	if (local_txd->srcbus.maxwidth == PL08X_CODING_ERR) {
		dev_err(&pd.dmac->dev,
			"%s - local_txd->srcbus.maxwidth codeing error cctl_parm.bits.swidth %d\n",
				__func__, cctl_parm.bits.swidth);
		return 0;
	}

	local_txd->srcbus.buswidth = local_txd->srcbus.maxwidth;
	local_txd->dstbus.maxwidth =
		pl08x_decode_widthbits(cctl_parm.bits.dwidth);

	if (local_txd->dstbus.maxwidth == PL08X_CODING_ERR) {
		dev_err(&pd.dmac->dev,
			"%s - local_txd->dstbus.maxwidth coding error - cctl_parm.bits.dwidth %d\n",
				__func__, cctl_parm.bits.dwidth);
		return 0;
	}

	local_txd->dstbus.buswidth = local_txd->dstbus.maxwidth;

	/*
	 *  Note bytes transferred == tsize * MIN(buswidths), not max(buswidths)
	 */
	max_bytes_per_lli = 2min(local_txd->srcbus.maxwidth,
					local_txd->dstbus.maxwidth) *
						cctl_parm.bits.tsize;

	remainder = local_txd->len;
	/*
	 * Choose bus to align to
	 * - prefers destination bus if both available
	 * - if fixed address on one bus chooses other
	 */
	pl08x_choose_master_bus(&local_txd->srcbus,
		&local_txd->dstbus, &mbus, &sbus, &cctl_parm);

	if (local_txd->len < mbus->buswidth)3 {
		/*
		 * Less than a bus width available
		 * - send as single bytes
		 */
		while (remainder) {
			cctl_parm.bits.swidth = pl08x_encode_width(1);
			cctl_parm.bits.dwidth = pl08x_encode_width(1);
			cctl_parm.bits.tsize = 1;
			num_llis =
				pl08x_fill_lli_for_desc(local_txd, num_llis, 1,
					&cctl_parm, &remainder);
			total_bytes++;
		}
	} else {
		/*
		 *  Make one byte LLIs until master bus is aligned
		 *  - slave will then be aligned also
		 */
		while ((mbus->addr) % (mbus->buswidth)) {
			cctl_parm.bits.swidth = pl08x_encode_width(1);
			cctl_parm.bits.dwidth = pl08x_encode_width(1);
			cctl_parm.bits.tsize = 1;
			num_llis = pl08x_fill_lli_for_desc
				(local_txd, num_llis, 1, &cctl_parm,
					&remainder);
			total_bytes++;
		}
		/*
		 *  Master now aligned
		 * - if slave is not then we must set its width down
		 */
		if (sbus->addr % sbus->buswidth)
			sbus->buswidth = 1;

		/*
		 * Make largest possible LLIs until less than one bus width left
		 */
		while (remainder > (mbus->buswidth - 1)) {
			int lli_len, target_len;
			int tsize;
			int odd_bytes;
			/*
			 * If enough left try to send max possible,
			 * otherwise try to send the remainder
			 */
			target_len = remainder;
			if (remainder > max_bytes_per_lli)
				target_len = max_bytes_per_lli;
			/*
			 *  Set bus lengths for incrementing busses
			 *  to number of bytes which fill
			 *  to next memory boundary
			 */
			if (cctl_parm.bits.si)
				local_txd->srcbus.fill_bytes =
					pl08x_pre_boundary(
						local_txd->srcbus.addr,
						remainder);4
			else
				local_txd->srcbus.fill_bytes =
					max_bytes_per_lli;
			if (cctl_parm.bits.di)
				local_txd->dstbus.fill_bytes =
					pl08x_pre_boundary(
						local_txd->dstbus.addr,
						remainder);
			else
				local_txd->dstbus.fill_bytes =
						max_bytes_per_lli;
			/*
			 *  Find the nearest
			 */
			lli_len	= min(local_txd->srcbus.fill_bytes,
						local_txd->dstbus.fill_bytes);

			if (lli_len <= 0) {
				dev_err(&pd.dmac->dev,
					"%s - lli_len is %d, <= 0\n",
						__func__, lli_len);
				return 0;
			}

			if (lli_len == target_len) {
				/*
				 * Can send what we wanted
				 */
				/*
				 *  Maintain alignment
				 */
				lli_len	= (lli_len/mbus->buswidth) *
							mbus->buswidth;
				odd_bytes = 0;
			} else {
				/*
				 * So now we know how many bytes to transfer
				 * to get to the nearest boundary
				 * The next lli will past the boundary
				 * - however we may be working to a boundary
				 *   on the slave bus
				 *   We need to ensure the master stays aligned
				 */
				odd_bytes = lli_len % mbus->buswidth;
				/*
				 * - and that we are working in multiples
				 *   of the bus widths
				 */
				lli_len -= odd_bytes;

			}
			if (lli_len) {
				/*
				 * Check against minimum bus alignment
				 */
				target_len = lli_len;
				tsize = lli_len/min(mbus->buswidth,
							sbus->buswidth);
				lli_len	= tsize * min(mbus->buswidth,
							sbus->buswidth);

				if (target_len != lli_len) {
					dev_err(&pd.dmac->dev,
					"%s - can't send what we want. Desired %d, sent %d in transfer of %d\n",
					__func__, target_len, lli_len, local_txd->len);
					return 0;
				}

				cctl_parm.bits.swidth = pl08x_encode_width
					(local_txd->srcbus.buswidth);
				cctl_parm.bits.dwidth = pl08x_encode_width
					(local_txd->dstbus.buswidth);
				if ((cctl_parm.bits.swidth == PL08X_CODING_ERR) ||
					(cctl_parm.bits.dwidth == PL08X_CODING_ERR)) {
					dev_err(&pd.dmac->dev,
					"%s - cctl_parm.bits.swidth or dwidth coding error - local_txd->dstbus.buswidth %d, local_txd->srcbus.buswidth %d\n",
					__func__,
					local_txd->dstbus.buswidth,
					local_txd->srcbus.buswidth
					);
					return 0;
				}
				cctl_parm.bits.tsize = tsize;
				num_llis = pl08x_fill_lli_for_desc(local_txd,
						num_llis, lli_len, &cctl_parm,
						&remainder);
				total_bytes += lli_len;
			}
			if (odd_bytes) {
				/*
				 * Creep past the boundary,
				 * maintaining master alignment
				 */
				int j;
				for (j = 0; (j < mbus->buswidth)
						&& (remainder); j++) {
					cctl_parm.bits.swidth =
						pl08x_encode_width(1);
					cctl_parm.bits.dwidth =
						pl08x_encode_width(1);

					cctl_parm.bits.tsize = 1;
					num_llis =
						pl08x_fill_lli_for_desc(
							local_txd, num_llis, 1,
							&cctl_parm, &remainder);
					total_bytes++;
				}
			}
		}

		/*
		 * Send any odd bytes
		 */
		if (remainder < 0) {
			dev_err(&pd.dmac->dev, "%s - -ve remainder 0x%08x\n",
					__func__, remainder);
			return 0;
		}

		while (remainder) {
			cctl_parm.bits.swidth = pl08x_encode_width(1);
			cctl_parm.bits.dwidth = pl08x_encode_width(1);
			cctl_parm.bits.tsize = 1;
			num_llis = pl08x_fill_lli_for_desc(local_txd, num_llis,
					1, &cctl_parm, &remainder);
			total_bytes++;
		}
	}
	if (total_bytes != local_txd->len) {
		dev_err(&pd.dmac->dev,
			"%s - only transferred 0x%08x from size 0x%08x\n",
				__func__, total_bytes, local_txd->len);
		return 0;
	}

	if (num_llis >= MAX_NUM_TSFR_LLIS) {5
		dev_err(&pd.dmac->dev,
			"%s - need to increase MAX_NUM_TSFR_LLIS from 0x%08x\n",
				__func__, MAX_NUM_TSFR_LLIS);
		return 0;
	}
	/*
	 * Decide whether this is a loop or a terminated transfer
	 */
	llis_va = ((struct _lli *)local_txd->llis_va);
	llis_bus = ((struct _lli *)local_txd->llis_bus);

	if (client->circular_buffer) {6
		llis_va[num_llis - 1].next =
			(dma_addr_t)((unsigned int)&(llis_bus[0]) +
						pd.pd->bus_bit_lli);
	} else {
		/*
		 * Final LLI terminates
		 */

		llis_va[num_llis - 1].next = 0;7
		/*
		 * Final LLI interrupts
		 */
		llis_va[num_llis - 1].cctl.bits.intr = PL08X_CCTL_INTR_YES;8
	}

	/* Now store the channel register values */
	local_txd->csrc = llis_va[0].src;
	local_txd->cdst = llis_va[0].dst;
	if (num_llis > 1)
		local_txd->clli = llis_va[0].next;
	else
		local_txd->clli = 0;

	local_txd->cctl = llis_va[0].cctl.val;
	local_txd->ccfg = client->config_base;

	/*
	 * TODO: Change to use /proc data
	 */
	if (pd.max_num_llis < num_llis)
		pd.max_num_llis = num_llis;

	return num_llis;
}
        

1

从传入的配置中,解码出bus宽度,单位字节

2

从源总线和目的总线选出一个最小带宽,然后乘与一个传输的个数,得到单个LLI的最大允许的字节数

3

要传输的数据,比带宽还小,那简单地分成几个LLI就搞定了

4

检查数据有没有超过允许的范围,如果超过了,就用PL08X_BOUNDARY_SIZE=0x400=1KB

5

如果你一次要求传输数据太多,然后拆分成了太多个LLI,那么这里会告诉你超过限制了

6

如果是循环缓存circular buffer,那么就告诉DMA传完最后一个LLI的时候,继续从最这个LLI的链表的最开始一个传,这样就周而复始地传输了,一般适用于音频流数据

7

最后一个LLI的next LLI指针的值,一定要设置为NULL,表示DMA传输完这个LLI之后,就结束了

8

最后DMA传完所有的数据了,肯定要发生中断,然后此出pl08x的irq函数会被调用,然后会再接着调用你的驱动做DMA请求时候挂载的callback函数