162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright(c) 2016-2018 Intel Corporation. All rights reserved.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci#include <linux/dma-mapping.h>
662306a36Sopenharmony_ci#include <linux/mei.h>
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include "mei_dev.h"
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci/**
1162306a36Sopenharmony_ci * mei_dmam_dscr_alloc() - allocate a managed coherent buffer
1262306a36Sopenharmony_ci *     for the dma descriptor
1362306a36Sopenharmony_ci * @dev: mei_device
1462306a36Sopenharmony_ci * @dscr: dma descriptor
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * Return:
1762306a36Sopenharmony_ci * * 0       - on success or zero allocation request
1862306a36Sopenharmony_ci * * -EINVAL - if size is not power of 2
1962306a36Sopenharmony_ci * * -ENOMEM - of allocation has failed
2062306a36Sopenharmony_ci */
2162306a36Sopenharmony_cistatic int mei_dmam_dscr_alloc(struct mei_device *dev,
2262306a36Sopenharmony_ci			       struct mei_dma_dscr *dscr)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	if (!dscr->size)
2562306a36Sopenharmony_ci		return 0;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	if (WARN_ON(!is_power_of_2(dscr->size)))
2862306a36Sopenharmony_ci		return -EINVAL;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	if (dscr->vaddr)
3162306a36Sopenharmony_ci		return 0;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	dscr->vaddr = dmam_alloc_coherent(dev->dev, dscr->size, &dscr->daddr,
3462306a36Sopenharmony_ci					  GFP_KERNEL);
3562306a36Sopenharmony_ci	if (!dscr->vaddr)
3662306a36Sopenharmony_ci		return -ENOMEM;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	return 0;
3962306a36Sopenharmony_ci}
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci/**
4262306a36Sopenharmony_ci * mei_dmam_dscr_free() - free a managed coherent buffer
4362306a36Sopenharmony_ci *     from the dma descriptor
4462306a36Sopenharmony_ci * @dev: mei_device
4562306a36Sopenharmony_ci * @dscr: dma descriptor
4662306a36Sopenharmony_ci */
4762306a36Sopenharmony_cistatic void mei_dmam_dscr_free(struct mei_device *dev,
4862306a36Sopenharmony_ci			       struct mei_dma_dscr *dscr)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	if (!dscr->vaddr)
5162306a36Sopenharmony_ci		return;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	dmam_free_coherent(dev->dev, dscr->size, dscr->vaddr, dscr->daddr);
5462306a36Sopenharmony_ci	dscr->vaddr = NULL;
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci/**
5862306a36Sopenharmony_ci * mei_dmam_ring_free() - free dma ring buffers
5962306a36Sopenharmony_ci * @dev: mei device
6062306a36Sopenharmony_ci */
6162306a36Sopenharmony_civoid mei_dmam_ring_free(struct mei_device *dev)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	int i;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	for (i = 0; i < DMA_DSCR_NUM; i++)
6662306a36Sopenharmony_ci		mei_dmam_dscr_free(dev, &dev->dr_dscr[i]);
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci/**
7062306a36Sopenharmony_ci * mei_dmam_ring_alloc() - allocate dma ring buffers
7162306a36Sopenharmony_ci * @dev: mei device
7262306a36Sopenharmony_ci *
7362306a36Sopenharmony_ci * Return: -ENOMEM on allocation failure 0 otherwise
7462306a36Sopenharmony_ci */
7562306a36Sopenharmony_ciint mei_dmam_ring_alloc(struct mei_device *dev)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	int i;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	for (i = 0; i < DMA_DSCR_NUM; i++)
8062306a36Sopenharmony_ci		if (mei_dmam_dscr_alloc(dev, &dev->dr_dscr[i]))
8162306a36Sopenharmony_ci			goto err;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	return 0;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cierr:
8662306a36Sopenharmony_ci	mei_dmam_ring_free(dev);
8762306a36Sopenharmony_ci	return -ENOMEM;
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci/**
9162306a36Sopenharmony_ci * mei_dma_ring_is_allocated() - check if dma ring is allocated
9262306a36Sopenharmony_ci * @dev: mei device
9362306a36Sopenharmony_ci *
9462306a36Sopenharmony_ci * Return: true if dma ring is allocated
9562306a36Sopenharmony_ci */
9662306a36Sopenharmony_cibool mei_dma_ring_is_allocated(struct mei_device *dev)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	return !!dev->dr_dscr[DMA_DSCR_HOST].vaddr;
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic inline
10262306a36Sopenharmony_cistruct hbm_dma_ring_ctrl *mei_dma_ring_ctrl(struct mei_device *dev)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	return (struct hbm_dma_ring_ctrl *)dev->dr_dscr[DMA_DSCR_CTRL].vaddr;
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci/**
10862306a36Sopenharmony_ci * mei_dma_ring_reset() - reset the dma control block
10962306a36Sopenharmony_ci * @dev: mei device
11062306a36Sopenharmony_ci */
11162306a36Sopenharmony_civoid mei_dma_ring_reset(struct mei_device *dev)
11262306a36Sopenharmony_ci{
11362306a36Sopenharmony_ci	struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	if (!ctrl)
11662306a36Sopenharmony_ci		return;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	memset(ctrl, 0, sizeof(*ctrl));
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci/**
12262306a36Sopenharmony_ci * mei_dma_copy_from() - copy from dma ring into buffer
12362306a36Sopenharmony_ci * @dev: mei device
12462306a36Sopenharmony_ci * @buf: data buffer
12562306a36Sopenharmony_ci * @offset: offset in slots.
12662306a36Sopenharmony_ci * @n: number of slots to copy.
12762306a36Sopenharmony_ci */
12862306a36Sopenharmony_cistatic size_t mei_dma_copy_from(struct mei_device *dev, unsigned char *buf,
12962306a36Sopenharmony_ci				u32 offset, u32 n)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	unsigned char *dbuf = dev->dr_dscr[DMA_DSCR_DEVICE].vaddr;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	size_t b_offset = offset << 2;
13462306a36Sopenharmony_ci	size_t b_n = n << 2;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	memcpy(buf, dbuf + b_offset, b_n);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	return b_n;
13962306a36Sopenharmony_ci}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci/**
14262306a36Sopenharmony_ci * mei_dma_copy_to() - copy to a buffer to the dma ring
14362306a36Sopenharmony_ci * @dev: mei device
14462306a36Sopenharmony_ci * @buf: data buffer
14562306a36Sopenharmony_ci * @offset: offset in slots.
14662306a36Sopenharmony_ci * @n: number of slots to copy.
14762306a36Sopenharmony_ci */
14862306a36Sopenharmony_cistatic size_t mei_dma_copy_to(struct mei_device *dev, unsigned char *buf,
14962306a36Sopenharmony_ci			      u32 offset, u32 n)
15062306a36Sopenharmony_ci{
15162306a36Sopenharmony_ci	unsigned char *hbuf = dev->dr_dscr[DMA_DSCR_HOST].vaddr;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	size_t b_offset = offset << 2;
15462306a36Sopenharmony_ci	size_t b_n = n << 2;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	memcpy(hbuf + b_offset, buf, b_n);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	return b_n;
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci/**
16262306a36Sopenharmony_ci * mei_dma_ring_read() - read data from the ring
16362306a36Sopenharmony_ci * @dev: mei device
16462306a36Sopenharmony_ci * @buf: buffer to read into: may be NULL in case of droping the data.
16562306a36Sopenharmony_ci * @len: length to read.
16662306a36Sopenharmony_ci */
16762306a36Sopenharmony_civoid mei_dma_ring_read(struct mei_device *dev, unsigned char *buf, u32 len)
16862306a36Sopenharmony_ci{
16962306a36Sopenharmony_ci	struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev);
17062306a36Sopenharmony_ci	u32 dbuf_depth;
17162306a36Sopenharmony_ci	u32 rd_idx, rem, slots;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	if (WARN_ON(!ctrl))
17462306a36Sopenharmony_ci		return;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	dev_dbg(dev->dev, "reading from dma %u bytes\n", len);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	if (!len)
17962306a36Sopenharmony_ci		return;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	dbuf_depth = dev->dr_dscr[DMA_DSCR_DEVICE].size >> 2;
18262306a36Sopenharmony_ci	rd_idx = READ_ONCE(ctrl->dbuf_rd_idx) & (dbuf_depth - 1);
18362306a36Sopenharmony_ci	slots = mei_data2slots(len);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	/* if buf is NULL we drop the packet by advancing the pointer.*/
18662306a36Sopenharmony_ci	if (!buf)
18762306a36Sopenharmony_ci		goto out;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	if (rd_idx + slots > dbuf_depth) {
19062306a36Sopenharmony_ci		buf += mei_dma_copy_from(dev, buf, rd_idx, dbuf_depth - rd_idx);
19162306a36Sopenharmony_ci		rem = slots - (dbuf_depth - rd_idx);
19262306a36Sopenharmony_ci		rd_idx = 0;
19362306a36Sopenharmony_ci	} else {
19462306a36Sopenharmony_ci		rem = slots;
19562306a36Sopenharmony_ci	}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	mei_dma_copy_from(dev, buf, rd_idx, rem);
19862306a36Sopenharmony_ciout:
19962306a36Sopenharmony_ci	WRITE_ONCE(ctrl->dbuf_rd_idx, ctrl->dbuf_rd_idx + slots);
20062306a36Sopenharmony_ci}
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_cistatic inline u32 mei_dma_ring_hbuf_depth(struct mei_device *dev)
20362306a36Sopenharmony_ci{
20462306a36Sopenharmony_ci	return dev->dr_dscr[DMA_DSCR_HOST].size >> 2;
20562306a36Sopenharmony_ci}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci/**
20862306a36Sopenharmony_ci * mei_dma_ring_empty_slots() - calaculate number of empty slots in dma ring
20962306a36Sopenharmony_ci * @dev: mei_device
21062306a36Sopenharmony_ci *
21162306a36Sopenharmony_ci * Return: number of empty slots
21262306a36Sopenharmony_ci */
21362306a36Sopenharmony_ciu32 mei_dma_ring_empty_slots(struct mei_device *dev)
21462306a36Sopenharmony_ci{
21562306a36Sopenharmony_ci	struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev);
21662306a36Sopenharmony_ci	u32 wr_idx, rd_idx, hbuf_depth, empty;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	if (!mei_dma_ring_is_allocated(dev))
21962306a36Sopenharmony_ci		return 0;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	if (WARN_ON(!ctrl))
22262306a36Sopenharmony_ci		return 0;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	/* easier to work in slots */
22562306a36Sopenharmony_ci	hbuf_depth = mei_dma_ring_hbuf_depth(dev);
22662306a36Sopenharmony_ci	rd_idx = READ_ONCE(ctrl->hbuf_rd_idx);
22762306a36Sopenharmony_ci	wr_idx = READ_ONCE(ctrl->hbuf_wr_idx);
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	if (rd_idx > wr_idx)
23062306a36Sopenharmony_ci		empty = rd_idx - wr_idx;
23162306a36Sopenharmony_ci	else
23262306a36Sopenharmony_ci		empty = hbuf_depth - (wr_idx - rd_idx);
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	return empty;
23562306a36Sopenharmony_ci}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci/**
23862306a36Sopenharmony_ci * mei_dma_ring_write - write data to dma ring host buffer
23962306a36Sopenharmony_ci *
24062306a36Sopenharmony_ci * @dev: mei_device
24162306a36Sopenharmony_ci * @buf: data will be written
24262306a36Sopenharmony_ci * @len: data length
24362306a36Sopenharmony_ci */
24462306a36Sopenharmony_civoid mei_dma_ring_write(struct mei_device *dev, unsigned char *buf, u32 len)
24562306a36Sopenharmony_ci{
24662306a36Sopenharmony_ci	struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev);
24762306a36Sopenharmony_ci	u32 hbuf_depth;
24862306a36Sopenharmony_ci	u32 wr_idx, rem, slots;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	if (WARN_ON(!ctrl))
25162306a36Sopenharmony_ci		return;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	dev_dbg(dev->dev, "writing to dma %u bytes\n", len);
25462306a36Sopenharmony_ci	hbuf_depth = mei_dma_ring_hbuf_depth(dev);
25562306a36Sopenharmony_ci	wr_idx = READ_ONCE(ctrl->hbuf_wr_idx) & (hbuf_depth - 1);
25662306a36Sopenharmony_ci	slots = mei_data2slots(len);
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	if (wr_idx + slots > hbuf_depth) {
25962306a36Sopenharmony_ci		buf += mei_dma_copy_to(dev, buf, wr_idx, hbuf_depth - wr_idx);
26062306a36Sopenharmony_ci		rem = slots - (hbuf_depth - wr_idx);
26162306a36Sopenharmony_ci		wr_idx = 0;
26262306a36Sopenharmony_ci	} else {
26362306a36Sopenharmony_ci		rem = slots;
26462306a36Sopenharmony_ci	}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	mei_dma_copy_to(dev, buf, wr_idx, rem);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	WRITE_ONCE(ctrl->hbuf_wr_idx, ctrl->hbuf_wr_idx + slots);
26962306a36Sopenharmony_ci}
270