lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Date:   Tue, 17 Nov 2020 09:58:24 +0200
From:   Peter Ujfalusi <peter.ujfalusi@...com>
To:     Vinod Koul <vkoul@...nel.org>, <dmaengine@...r.kernel.org>
CC:     <linux-arm-msm@...r.kernel.org>,
        Bjorn Andersson <bjorn.andersson@...aro.org>,
        Rob Herring <robh+dt@...nel.org>, <devicetree@...r.kernel.org>,
        <linux-kernel@...r.kernel.org>
Subject: Re: [PATCH v6 3/3] dmaengine: qcom: Add GPI dma driver

Hi Vinod,

On 09/11/2020 10.54, Vinod Koul wrote:
> This controller provides DMAengine capabilities for a variety of peripheral
> buses such as I2C, UART, and SPI. By using GPI dmaengine driver, bus
> drivers can use a standardize interface that is protocol independent to
> transfer data between memory and peripheral.

There is something I don't quite follow regarding to tracking the issued
and in progress transfers...

...

> diff --git a/drivers/dma/qcom/gpi.c b/drivers/dma/qcom/gpi.c
> new file mode 100644
> index 000000000000..d2334f535de2
> --- /dev/null
> +++ b/drivers/dma/qcom/gpi.c

...

> +/* process DMA Immediate completion data events */
> +static void gpi_process_imed_data_event(struct gchan *gchan,
> +					struct immediate_data_event *imed_event)
> +{
> +	struct gpii *gpii = gchan->gpii;
> +	struct gpi_ring *ch_ring = &gchan->ch_ring;
> +	void *tre = ch_ring->base + (ch_ring->el_size * imed_event->tre_index);
> +	struct dmaengine_result result;
> +	struct gpi_desc *gpi_desc;
> +	struct virt_dma_desc *vd;
> +	unsigned long flags;
> +	u32 chid;
> +
> +	/*
> +	 * If channel not active don't process event
> +	 */
> +	if (gchan->pm_state != ACTIVE_STATE) {
> +		dev_err(gpii->gpi_dev->dev, "skipping processing event because ch @ %s state\n",
> +			TO_GPI_PM_STR(gchan->pm_state));
> +		return;
> +	}
> +
> +	spin_lock_irqsave(&gchan->vc.lock, flags);
> +	vd = vchan_next_desc(&gchan->vc);

you take the first entry from the desc_issued list.
The one you actually started is the last one from the list.

> +	if (!vd) {
> +		struct gpi_ere *gpi_ere;
> +		struct gpi_tre *gpi_tre;
> +
> +		spin_unlock_irqrestore(&gchan->vc.lock, flags);
> +		dev_dbg(gpii->gpi_dev->dev, "event without a pending descriptor!\n");
> +		gpi_ere = (struct gpi_ere *)imed_event;
> +		dev_dbg(gpii->gpi_dev->dev,
> +			"Event: %08x %08x %08x %08x\n",
> +			gpi_ere->dword[0], gpi_ere->dword[1],
> +			gpi_ere->dword[2], gpi_ere->dword[3]);
> +		gpi_tre = tre;
> +		dev_dbg(gpii->gpi_dev->dev,
> +			"Pending TRE: %08x %08x %08x %08x\n",
> +			gpi_tre->dword[0], gpi_tre->dword[1],
> +			gpi_tre->dword[2], gpi_tre->dword[3]);
> +		return;
> +	}
> +	gpi_desc = to_gpi_desc(vd);
> +	spin_unlock_irqrestore(&gchan->vc.lock, flags);
> +
> +	/*
> +	 * RP pointed by Event is to last TRE processed,
> +	 * we need to update ring rp to tre + 1
> +	 */
> +	tre += ch_ring->el_size;
> +	if (tre >= (ch_ring->base + ch_ring->len))
> +		tre = ch_ring->base;
> +	ch_ring->rp = tre;
> +
> +	/* make sure rp updates are immediately visible to all cores */
> +	smp_wmb();
> +
> +	chid = imed_event->chid;
> +	if (imed_event->code == MSM_GPI_TCE_EOT && gpii->ieob_set) {
> +		if (chid == GPI_RX_CHAN)
> +			goto gpi_free_desc;
> +		else
> +			return;
> +	}
> +
> +	if (imed_event->code == MSM_GPI_TCE_UNEXP_ERR)
> +		result.result = DMA_TRANS_ABORTED;
> +	else
> +		result.result = DMA_TRANS_NOERROR;
> +	result.residue = gpi_desc->len - imed_event->length;
> +
> +	dma_cookie_complete(&vd->tx);
> +	dmaengine_desc_get_callback_invoke(&vd->tx, &result);
> +
> +gpi_free_desc:
> +	spin_lock_irqsave(&gchan->vc.lock, flags);
> +	list_del(&vd->node);
> +	spin_unlock_irqrestore(&gchan->vc.lock, flags);
> +	kfree(gpi_desc);
> +	gpi_desc = NULL;
> +}
> +
> +/* processing transfer completion events */
> +static void gpi_process_xfer_compl_event(struct gchan *gchan,
> +					 struct xfer_compl_event *compl_event)
> +{
> +	struct gpii *gpii = gchan->gpii;
> +	struct gpi_ring *ch_ring = &gchan->ch_ring;
> +	void *ev_rp = to_virtual(ch_ring, compl_event->ptr);
> +	struct virt_dma_desc *vd;
> +	struct gpi_desc *gpi_desc;
> +	struct dmaengine_result result;
> +	unsigned long flags;
> +	u32 chid;
> +
> +	/* only process events on active channel */
> +	if (unlikely(gchan->pm_state != ACTIVE_STATE)) {
> +		dev_err(gpii->gpi_dev->dev, "skipping processing event because ch @ %s state\n",
> +			TO_GPI_PM_STR(gchan->pm_state));
> +		return;
> +	}
> +
> +	spin_lock_irqsave(&gchan->vc.lock, flags);
> +	vd = vchan_next_desc(&gchan->vc);

you take the first entry from the desc_issued list here as well.
The one you actually started is the last one from the list.

> +	if (!vd) {
> +		struct gpi_ere *gpi_ere;
> +
> +		spin_unlock_irqrestore(&gchan->vc.lock, flags);
> +		dev_err(gpii->gpi_dev->dev, "Event without a pending descriptor!\n");
> +		gpi_ere = (struct gpi_ere *)compl_event;
> +		dev_err(gpii->gpi_dev->dev,
> +			"Event: %08x %08x %08x %08x\n",
> +			gpi_ere->dword[0], gpi_ere->dword[1],
> +			gpi_ere->dword[2], gpi_ere->dword[3]);
> +		return;
> +	}
> +
> +	gpi_desc = to_gpi_desc(vd);
> +	spin_unlock_irqrestore(&gchan->vc.lock, flags);
> +
> +	/*
> +	 * RP pointed by Event is to last TRE processed,
> +	 * we need to update ring rp to ev_rp + 1
> +	 */
> +	ev_rp += ch_ring->el_size;
> +	if (ev_rp >= (ch_ring->base + ch_ring->len))
> +		ev_rp = ch_ring->base;
> +	ch_ring->rp = ev_rp;
> +
> +	/* update must be visible to other cores */
> +	smp_wmb();
> +
> +	chid = compl_event->chid;
> +	if (compl_event->code == MSM_GPI_TCE_EOT && gpii->ieob_set) {
> +		if (chid == GPI_RX_CHAN)
> +			goto gpi_free_desc;
> +		else
> +			return;
> +	}
> +
> +	if (compl_event->code == MSM_GPI_TCE_UNEXP_ERR) {
> +		dev_err(gpii->gpi_dev->dev, "Error in Transaction\n");
> +		result.result = DMA_TRANS_ABORTED;
> +	} else {
> +		dev_dbg(gpii->gpi_dev->dev, "Transaction Success\n");
> +		result.result = DMA_TRANS_NOERROR;
> +	}
> +	result.residue = gpi_desc->len - compl_event->length;
> +	dev_dbg(gpii->gpi_dev->dev, "Residue %d\n", result.residue);
> +
> +	dma_cookie_complete(&vd->tx);
> +	dmaengine_desc_get_callback_invoke(&vd->tx, &result);

If you had two descriptors in the list, you have started the last one
and here you would complete the first one, which is not even started...

> +
> +gpi_free_desc:
> +	spin_lock_irqsave(&gchan->vc.lock, flags);
> +	list_del(&vd->node);
> +	spin_unlock_irqrestore(&gchan->vc.lock, flags);
> +	kfree(gpi_desc);
> +	gpi_desc = NULL;
> +}
> +
> +/* process all events */
> +static void gpi_process_events(struct gpii *gpii)
> +{
> +	struct gpi_ring *ev_ring = &gpii->ev_ring;
> +	phys_addr_t cntxt_rp;
> +	void *rp;
> +	union gpi_event *gpi_event;
> +	struct gchan *gchan;
> +	u32 chid, type;
> +
> +	cntxt_rp = gpi_read_reg(gpii, gpii->ev_ring_rp_lsb_reg);
> +	rp = to_virtual(ev_ring, cntxt_rp);
> +
> +	do {
> +		while (rp != ev_ring->rp) {
> +			gpi_event = ev_ring->rp;
> +			chid = gpi_event->xfer_compl_event.chid;
> +			type = gpi_event->xfer_compl_event.type;
> +
> +			dev_dbg(gpii->gpi_dev->dev,
> +				"Event: CHID:%u, type:%x %08x %08x %08x %08x\n",
> +				chid, type, gpi_event->gpi_ere.dword[0],
> +				gpi_event->gpi_ere.dword[1], gpi_event->gpi_ere.dword[2],
> +				gpi_event->gpi_ere.dword[3]);
> +
> +			switch (type) {
> +			case XFER_COMPLETE_EV_TYPE:
> +				gchan = &gpii->gchan[chid];
> +				gpi_process_xfer_compl_event(gchan,
> +							     &gpi_event->xfer_compl_event);
> +				break;
> +			case STALE_EV_TYPE:
> +				dev_dbg(gpii->gpi_dev->dev, "stale event, not processing\n");
> +				break;
> +			case IMMEDIATE_DATA_EV_TYPE:
> +				gchan = &gpii->gchan[chid];
> +				gpi_process_imed_data_event(gchan,
> +							    &gpi_event->immediate_data_event);
> +				break;
> +			case QUP_NOTIF_EV_TYPE:
> +				dev_dbg(gpii->gpi_dev->dev, "QUP_NOTIF_EV_TYPE\n");
> +				break;
> +			default:
> +				dev_dbg(gpii->gpi_dev->dev,
> +					"not supported event type:0x%x\n", type);
> +			}
> +			gpi_ring_recycle_ev_element(ev_ring);
> +		}
> +		gpi_write_ev_db(gpii, ev_ring, ev_ring->wp);
> +
> +		/* clear pending IEOB events */
> +		gpi_write_reg(gpii, gpii->ieob_clr_reg, BIT(0));
> +
> +		cntxt_rp = gpi_read_reg(gpii, gpii->ev_ring_rp_lsb_reg);
> +		rp = to_virtual(ev_ring, cntxt_rp);
> +
> +	} while (rp != ev_ring->rp);
> +}

...

> +/* rings transfer ring db to being transfer */
> +static void gpi_issue_pending(struct dma_chan *chan)
> +{
> +	struct gchan *gchan = to_gchan(chan);
> +	struct gpii *gpii = gchan->gpii;
> +	unsigned long flags, pm_lock_flags;
> +	struct virt_dma_desc *vd = NULL;
> +	struct gpi_desc *gpi_desc;
> +	struct gpi_ring *ch_ring = &gchan->ch_ring;
> +	void *tre, *wp = NULL;
> +	int i;
> +
> +	read_lock_irqsave(&gpii->pm_lock, pm_lock_flags);
> +
> +	/* move all submitted discriptors to issued list */
> +	spin_lock_irqsave(&gchan->vc.lock, flags);
> +	if (vchan_issue_pending(&gchan->vc))
> +		vd = list_last_entry(&gchan->vc.desc_issued,
> +				     struct virt_dma_desc, node);

You take the last entry from the desc_issued and start it.
vchan_next_desc() should be used here as well?

I think it is fine to leave the desc in the issued list after they are
given to the HW, but I prefer to delete it and save a pointer to the
channel as the curerntly running one.
The desc_issued then contains the descriptors which are issued, but not
yet submitted to the DMA.

In the completion one can just check if the gchan->desc (or whatever you
call it) is not NULL to catch spurious events.

Then in completion I take a look at the vchan_next_desc() to see if I
have another issued transfer pending and if there is then I start it
right away.

> +	spin_unlock_irqrestore(&gchan->vc.lock, flags);
> +
> +	/* nothing to do list is empty */
> +	if (!vd) {
> +		read_unlock_irqrestore(&gpii->pm_lock, pm_lock_flags);
> +		return;
> +	}
> +
> +	gpi_desc = to_gpi_desc(vd);
> +	for (i = 0; i < gpi_desc->num_tre; i++) {
> +		tre = &gpi_desc->tre[i];
> +		gpi_queue_xfer(gpii, gchan, tre, &wp);
> +	}
> +
> +	gpi_desc->db = ch_ring->wp;
> +	gpi_write_ch_db(gchan, &gchan->ch_ring, gpi_desc->db);
> +	read_unlock_irqrestore(&gpii->pm_lock, pm_lock_flags);
> +}
> +
> +static int gpi_ch_init(struct gchan *gchan)
> +{
> +	struct gpii *gpii = gchan->gpii;
> +	const int ev_factor = gpii->gpi_dev->ev_factor;
> +	u32 elements;
> +	int i = 0, ret = 0;
> +
> +	gchan->pm_state = CONFIG_STATE;
> +
> +	/* check if both channels are configured before continue */
> +	for (i = 0; i < MAX_CHANNELS_PER_GPII; i++)
> +		if (gpii->gchan[i].pm_state != CONFIG_STATE)
> +			goto exit_gpi_init;
> +
> +	/* protocol must be same for both channels */
> +	if (gpii->gchan[0].protocol != gpii->gchan[1].protocol) {
> +		dev_err(gpii->gpi_dev->dev, "protocol did not match protocol %u != %u\n",
> +			gpii->gchan[0].protocol, gpii->gchan[1].protocol);
> +		ret = -EINVAL;
> +		goto exit_gpi_init;
> +	}
> +
> +	/* allocate memory for event ring */
> +	elements = CHAN_TRES << ev_factor;
> +	ret = gpi_alloc_ring(&gpii->ev_ring, elements,
> +			     sizeof(union gpi_event), gpii);
> +	if (ret)
> +		goto exit_gpi_init;
> +
> +	/* configure interrupts */
> +	write_lock_irq(&gpii->pm_lock);
> +	gpii->pm_state = PREPARE_HARDWARE;
> +	write_unlock_irq(&gpii->pm_lock);
> +	ret = gpi_config_interrupts(gpii, DEFAULT_IRQ_SETTINGS, 0);
> +	if (ret) {
> +		dev_err(gpii->gpi_dev->dev, "error config. interrupts, ret:%d\n", ret);
> +		goto error_config_int;
> +	}
> +
> +	/* allocate event rings */
> +	ret = gpi_alloc_ev_chan(gpii);
> +	if (ret) {
> +		dev_err(gpii->gpi_dev->dev, "error alloc_ev_chan:%d\n", ret);
> +		goto error_alloc_ev_ring;
> +	}
> +
> +	/* Allocate all channels */
> +	for (i = 0; i < MAX_CHANNELS_PER_GPII; i++) {
> +		ret = gpi_alloc_chan(&gpii->gchan[i], true);
> +		if (ret) {
> +			dev_err(gpii->gpi_dev->dev, "Error allocating chan:%d\n", ret);
> +			goto error_alloc_chan;
> +		}
> +	}
> +
> +	/* start channels  */
> +	for (i = 0; i < MAX_CHANNELS_PER_GPII; i++) {
> +		ret = gpi_start_chan(&gpii->gchan[i]);
> +		if (ret) {
> +			dev_err(gpii->gpi_dev->dev, "Error start chan:%d\n", ret);
> +			goto error_start_chan;
> +		}
> +	}
> +	return ret;
> +
> +error_start_chan:
> +	for (i = i - 1; i >= 0; i++) {
> +		gpi_stop_chan(&gpii->gchan[i]);
> +		gpi_send_cmd(gpii, gchan, GPI_CH_CMD_RESET);
> +	}
> +	i = 2;
> +error_alloc_chan:
> +	for (i = i - 1; i >= 0; i--)
> +		gpi_reset_chan(gchan, GPI_CH_CMD_DE_ALLOC);
> +error_alloc_ev_ring:
> +	gpi_disable_interrupts(gpii);
> +error_config_int:
> +	gpi_free_ring(&gpii->ev_ring, gpii);
> +exit_gpi_init:
> +	mutex_unlock(&gpii->ctrl_lock);
> +	return ret;
> +}
> +
> +/* release all channel resources */
> +static void gpi_free_chan_resources(struct dma_chan *chan)
> +{
> +	struct gchan *gchan = to_gchan(chan);
> +	struct gpii *gpii = gchan->gpii;
> +	enum gpi_pm_state cur_state;
> +	int ret, i;
> +
> +	mutex_lock(&gpii->ctrl_lock);
> +
> +	cur_state = gchan->pm_state;
> +
> +	/* disable ch state so no more TRE processing for this channel */
> +	write_lock_irq(&gpii->pm_lock);
> +	gchan->pm_state = PREPARE_TERMINATE;
> +	write_unlock_irq(&gpii->pm_lock);
> +
> +	/* attempt to do graceful hardware shutdown */
> +	if (cur_state == ACTIVE_STATE) {
> +		gpi_stop_chan(gchan);
> +
> +		ret = gpi_send_cmd(gpii, gchan, GPI_CH_CMD_RESET);
> +		if (ret)
> +			dev_err(gpii->gpi_dev->dev, "error resetting channel:%d\n", ret);
> +
> +		gpi_reset_chan(gchan, GPI_CH_CMD_DE_ALLOC);
> +	}
> +
> +	/* free all allocated memory */
> +	gpi_free_ring(&gchan->ch_ring, gpii);
> +	vchan_free_chan_resources(&gchan->vc);
> +	kfree(gchan->config);
> +
> +	write_lock_irq(&gpii->pm_lock);
> +	gchan->pm_state = DISABLE_STATE;
> +	write_unlock_irq(&gpii->pm_lock);
> +
> +	/* if other rings are still active exit */
> +	for (i = 0; i < MAX_CHANNELS_PER_GPII; i++)
> +		if (gpii->gchan[i].ch_ring.configured)
> +			goto exit_free;
> +
> +	/* deallocate EV Ring */
> +	cur_state = gpii->pm_state;
> +	write_lock_irq(&gpii->pm_lock);
> +	gpii->pm_state = PREPARE_TERMINATE;
> +	write_unlock_irq(&gpii->pm_lock);
> +
> +	/* wait for threads to complete out */
> +	tasklet_kill(&gpii->ev_task);
> +
> +	/* send command to de allocate event ring */
> +	if (cur_state == ACTIVE_STATE)
> +		gpi_send_cmd(gpii, NULL, GPI_EV_CMD_DEALLOC);
> +
> +	gpi_free_ring(&gpii->ev_ring, gpii);
> +
> +	/* disable interrupts */
> +	if (cur_state == ACTIVE_STATE)
> +		gpi_disable_interrupts(gpii);
> +
> +	/* set final state to disable */
> +	write_lock_irq(&gpii->pm_lock);
> +	gpii->pm_state = DISABLE_STATE;
> +	write_unlock_irq(&gpii->pm_lock);
> +
> +exit_free:
> +	mutex_unlock(&gpii->ctrl_lock);
> +}
> +
> +/* allocate channel resources */
> +static int gpi_alloc_chan_resources(struct dma_chan *chan)
> +{
> +	struct gchan *gchan = to_gchan(chan);
> +	struct gpii *gpii = gchan->gpii;
> +	int ret;
> +
> +	mutex_lock(&gpii->ctrl_lock);
> +
> +	/* allocate memory for transfer ring */
> +	ret = gpi_alloc_ring(&gchan->ch_ring, CHAN_TRES,
> +			     sizeof(struct gpi_tre), gpii);
> +	if (ret)
> +		goto xfer_alloc_err;
> +
> +	ret = gpi_ch_init(gchan);
> +
> +	mutex_unlock(&gpii->ctrl_lock);
> +
> +	return ret;
> +xfer_alloc_err:
> +	mutex_unlock(&gpii->ctrl_lock);
> +
> +	return ret;
> +}
> +
> +static int gpi_find_avail_gpii(struct gpi_dev *gpi_dev, u32 seid)
> +{
> +	struct gchan *tx_chan, *rx_chan;
> +	unsigned int gpii;
> +
> +	/* check if same seid is already configured for another chid */
> +	for (gpii = 0; gpii < gpi_dev->max_gpii; gpii++) {
> +		if (!((1 << gpii) & gpi_dev->gpii_mask))
> +			continue;
> +
> +		tx_chan = &gpi_dev->gpiis[gpii].gchan[GPI_TX_CHAN];
> +		rx_chan = &gpi_dev->gpiis[gpii].gchan[GPI_RX_CHAN];
> +
> +		if (rx_chan->vc.chan.client_count && rx_chan->seid == seid)
> +			return gpii;
> +		if (tx_chan->vc.chan.client_count && tx_chan->seid == seid)
> +			return gpii;
> +	}
> +
> +	/* no channels configured with same seid, return next avail gpii */
> +	for (gpii = 0; gpii < gpi_dev->max_gpii; gpii++) {
> +		if (!((1 << gpii) & gpi_dev->gpii_mask))
> +			continue;
> +
> +		tx_chan = &gpi_dev->gpiis[gpii].gchan[GPI_TX_CHAN];
> +		rx_chan = &gpi_dev->gpiis[gpii].gchan[GPI_RX_CHAN];
> +
> +		/* check if gpii is configured */
> +		if (tx_chan->vc.chan.client_count ||
> +		    rx_chan->vc.chan.client_count)
> +			continue;
> +
> +		/* found a free gpii */
> +		return gpii;
> +	}
> +
> +	/* no gpii instance available to use */
> +	return -EIO;
> +}
> +
> +/* gpi_of_dma_xlate: open client requested channel */
> +static struct dma_chan *gpi_of_dma_xlate(struct of_phandle_args *args,
> +					 struct of_dma *of_dma)
> +{
> +	struct gpi_dev *gpi_dev = (struct gpi_dev *)of_dma->of_dma_data;
> +	u32 seid, chid;
> +	int gpii;
> +	struct gchan *gchan;
> +
> +	if (args->args_count < 3) {
> +		dev_err(gpi_dev->dev, "gpii require minimum 2 args, client passed:%d args\n",
> +			args->args_count);
> +		return NULL;
> +	}
> +
> +	chid = args->args[0];
> +	if (chid >= MAX_CHANNELS_PER_GPII) {
> +		dev_err(gpi_dev->dev, "gpii channel:%d not valid\n", chid);
> +		return NULL;
> +	}
> +
> +	seid = args->args[1];
> +
> +	/* find next available gpii to use */
> +	gpii = gpi_find_avail_gpii(gpi_dev, seid);
> +	if (gpii < 0) {
> +		dev_err(gpi_dev->dev, "no available gpii instances\n");
> +		return NULL;
> +	}
> +
> +	gchan = &gpi_dev->gpiis[gpii].gchan[chid];
> +	if (gchan->vc.chan.client_count) {
> +		dev_err(gpi_dev->dev, "gpii:%d chid:%d seid:%d already configured\n",
> +			gpii, chid, gchan->seid);
> +		return NULL;
> +	}
> +
> +	gchan->seid = seid;
> +	gchan->protocol = args->args[2];
> +
> +	return dma_get_slave_channel(&gchan->vc.chan);
> +}
> +
> +static int gpi_probe(struct platform_device *pdev)
> +{
> +	struct gpi_dev *gpi_dev;
> +	unsigned int i;
> +	int ret;
> +
> +	gpi_dev = devm_kzalloc(&pdev->dev, sizeof(*gpi_dev), GFP_KERNEL);
> +	if (!gpi_dev)
> +		return -ENOMEM;
> +
> +	gpi_dev->dev = &pdev->dev;
> +	gpi_dev->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	gpi_dev->regs = devm_ioremap_resource(gpi_dev->dev, gpi_dev->res);
> +	if (IS_ERR(gpi_dev->regs))
> +		return PTR_ERR(gpi_dev->regs);
> +	gpi_dev->ee_base = gpi_dev->regs;
> +
> +	ret = of_property_read_u32(gpi_dev->dev->of_node, "dma-channels",
> +				   &gpi_dev->max_gpii);
> +	if (ret) {
> +		dev_err(gpi_dev->dev, "missing 'max-no-gpii' DT node\n");
> +		return ret;
> +	}
> +
> +	ret = of_property_read_u32(gpi_dev->dev->of_node, "dma-channel-mask",
> +				   &gpi_dev->gpii_mask);
> +	if (ret) {
> +		dev_err(gpi_dev->dev, "missing 'gpii-mask' DT node\n");
> +		return ret;
> +	}
> +
> +	gpi_dev->ev_factor = EV_FACTOR;
> +
> +	ret = dma_set_mask(gpi_dev->dev, DMA_BIT_MASK(64));
> +	if (ret) {
> +		dev_err(gpi_dev->dev, "Error setting dma_mask to 64, ret:%d\n", ret);
> +		return ret;
> +	}
> +
> +	gpi_dev->gpiis = devm_kzalloc(gpi_dev->dev, sizeof(*gpi_dev->gpiis) *
> +				      gpi_dev->max_gpii, GFP_KERNEL);
> +	if (!gpi_dev->gpiis)
> +		return -ENOMEM;
> +
> +	/* setup all the supported gpii */
> +	INIT_LIST_HEAD(&gpi_dev->dma_device.channels);
> +	for (i = 0; i < gpi_dev->max_gpii; i++) {
> +		struct gpii *gpii = &gpi_dev->gpiis[i];
> +		int chan;
> +
> +		if (!((1 << i) & gpi_dev->gpii_mask))
> +			continue;
> +
> +		/* set up ev cntxt register map */
> +		gpii->ev_cntxt_base_reg = gpi_dev->ee_base + GPII_n_EV_CH_k_CNTXT_0_OFFS(i, 0);
> +		gpii->ev_cntxt_db_reg = gpi_dev->ee_base + GPII_n_EV_CH_k_DOORBELL_0_OFFS(i, 0);
> +		gpii->ev_ring_rp_lsb_reg = gpii->ev_cntxt_base_reg + CNTXT_4_RING_RP_LSB;
> +		gpii->ev_cmd_reg = gpi_dev->ee_base + GPII_n_EV_CH_CMD_OFFS(i);
> +		gpii->ieob_clr_reg = gpi_dev->ee_base + GPII_n_CNTXT_SRC_IEOB_IRQ_CLR_OFFS(i);
> +
> +		/* set up irq */
> +		ret = platform_get_irq(pdev, i);
> +		if (ret < 0) {
> +			dev_err(gpi_dev->dev, "platform_get_irq failed for %d:%d\n", i, ret);
> +			return ret;
> +		}
> +		gpii->irq = ret;
> +
> +		/* set up channel specific register info */
> +		for (chan = 0; chan < MAX_CHANNELS_PER_GPII; chan++) {
> +			struct gchan *gchan = &gpii->gchan[chan];
> +
> +			/* set up ch cntxt register map */
> +			gchan->ch_cntxt_base_reg = gpi_dev->ee_base +
> +				GPII_n_CH_k_CNTXT_0_OFFS(i, chan);
> +			gchan->ch_cntxt_db_reg = gpi_dev->ee_base +
> +				GPII_n_CH_k_DOORBELL_0_OFFS(i, chan);
> +			gchan->ch_cmd_reg = gpi_dev->ee_base + GPII_n_CH_CMD_OFFS(i);
> +
> +			/* vchan setup */
> +			vchan_init(&gchan->vc, &gpi_dev->dma_device);
> +			gchan->vc.desc_free = gpi_desc_free;
> +			gchan->chid = chan;
> +			gchan->gpii = gpii;
> +			gchan->dir = GPII_CHAN_DIR[chan];
> +		}
> +		mutex_init(&gpii->ctrl_lock);
> +		rwlock_init(&gpii->pm_lock);
> +		tasklet_init(&gpii->ev_task, gpi_ev_tasklet,
> +			     (unsigned long)gpii);
> +		init_completion(&gpii->cmd_completion);
> +		gpii->gpii_id = i;
> +		gpii->regs = gpi_dev->ee_base;
> +		gpii->gpi_dev = gpi_dev;
> +	}
> +
> +	platform_set_drvdata(pdev, gpi_dev);
> +
> +	/* clear and Set capabilities */
> +	dma_cap_zero(gpi_dev->dma_device.cap_mask);
> +	dma_cap_set(DMA_SLAVE, gpi_dev->dma_device.cap_mask);
> +
> +	/* configure dmaengine apis */
> +	gpi_dev->dma_device.directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
> +	gpi_dev->dma_device.residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
> +	gpi_dev->dma_device.src_addr_widths = DMA_SLAVE_BUSWIDTH_8_BYTES;
> +	gpi_dev->dma_device.dst_addr_widths = DMA_SLAVE_BUSWIDTH_8_BYTES;
> +	gpi_dev->dma_device.device_alloc_chan_resources = gpi_alloc_chan_resources;
> +	gpi_dev->dma_device.device_free_chan_resources = gpi_free_chan_resources;
> +	gpi_dev->dma_device.device_tx_status = dma_cookie_status;
> +	gpi_dev->dma_device.device_issue_pending = gpi_issue_pending;
> +	gpi_dev->dma_device.device_prep_slave_sg = gpi_prep_slave_sg;
> +	gpi_dev->dma_device.device_config = gpi_peripheral_config;
> +	gpi_dev->dma_device.device_terminate_all = gpi_terminate_all;
> +	gpi_dev->dma_device.dev = gpi_dev->dev;
> +	gpi_dev->dma_device.device_pause = gpi_pause;
> +	gpi_dev->dma_device.device_resume = gpi_resume;
> +
> +	/* register with dmaengine framework */
> +	ret = dma_async_device_register(&gpi_dev->dma_device);
> +	if (ret) {
> +		dev_err(gpi_dev->dev, "async_device_register failed ret:%d", ret);
> +		return ret;
> +	}
> +
> +	ret = of_dma_controller_register(gpi_dev->dev->of_node,
> +					 gpi_of_dma_xlate, gpi_dev);
> +	if (ret) {
> +		dev_err(gpi_dev->dev, "of_dma_controller_reg failed ret:%d", ret);
> +		return ret;
> +	}
> +
> +	return ret;
> +}
> +
> +static const struct of_device_id gpi_of_match[] = {
> +	{ .compatible = "qcom,sdm845-gpi-dma" },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, gpi_of_match);
> +
> +static struct platform_driver gpi_driver = {
> +	.probe = gpi_probe,
> +	.driver = {
> +		.name = KBUILD_MODNAME,
> +		.of_match_table = gpi_of_match,
> +	},
> +};
> +
> +static int __init gpi_init(void)
> +{
> +	return platform_driver_register(&gpi_driver);
> +}
> +subsys_initcall(gpi_init)
> +
> +MODULE_DESCRIPTION("QCOM GPI DMA engine driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/dma/qcom-gpi-dma.h b/include/linux/dma/qcom-gpi-dma.h
> new file mode 100644
> index 000000000000..f46dc3372f11
> --- /dev/null
> +++ b/include/linux/dma/qcom-gpi-dma.h
> @@ -0,0 +1,83 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2020, Linaro Limited
> + */
> +
> +#ifndef QCOM_GPI_DMA_H
> +#define QCOM_GPI_DMA_H
> +
> +/**
> + * enum spi_transfer_cmd - spi transfer commands
> + */
> +enum spi_transfer_cmd {
> +	SPI_TX = 1,
> +	SPI_RX,
> +	SPI_DUPLEX,
> +};
> +
> +/**
> + * struct gpi_spi_config - spi config for peripheral
> + *
> + * @loopback_en: spi loopback enable when set
> + * @clock_pol_high: clock polarity
> + * @data_pol_high: data polarity
> + * @pack_en: process tx/rx buffers as packed
> + * @word_len: spi word length
> + * @clk_div: source clock divider
> + * @clk_src: serial clock
> + * @cmd: spi cmd
> + * @fragmentation: keep CS assserted at end of sequence
> + * @cs: chip select toggle
> + * @set_config: set peripheral config
> + * @rx_len: receive length for buffer
> + */
> +struct gpi_spi_config {
> +	u8 set_config;
> +	u8 loopback_en;
> +	u8 clock_pol_high;
> +	u8 data_pol_high;
> +	u8 pack_en;
> +	u8 word_len;
> +	u8 fragmentation;
> +	u8 cs;
> +	u32 clk_div;
> +	u32 clk_src;
> +	enum spi_transfer_cmd cmd;
> +	u32 rx_len;
> +};
> +
> +enum i2c_op {
> +	I2C_WRITE = 1,
> +	I2C_READ,
> +};
> +
> +/**
> + * struct gpi_i2c_config - i2c config for peripheral
> + *
> + * @pack_enable: process tx/rx buffers as packed
> + * @cycle_count: clock cycles to be sent
> + * @high_count: high period of clock
> + * @low_count: low period of clock
> + * @clk_div: source clock divider
> + * @addr: i2c bus address
> + * @stretch: stretch the clock at eot
> + * @set_config: set peripheral config
> + * @rx_len: receive length for buffer
> + * @op: i2c cmd
> + * @muli-msg: is part of multi i2c r-w msgs
> + */
> +struct gpi_i2c_config {
> +	u8 set_config;
> +	u8 pack_enable;
> +	u8 cycle_count;
> +	u8 high_count;
> +	u8 low_count;
> +	u8 addr;
> +	u8 stretch;
> +	u16 clk_div;
> +	u32 rx_len;
> +	enum i2c_op op;
> +	bool multi_msg;
> +};
> +
> +#endif /* QCOM_GPI_DMA_H */
> 

- Péter

Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ