// SPDX-License-Identifier: GPL-2.0 
 | 
/* 
 | 
 * Copyright(c) 2016-2018 Intel Corporation. All rights reserved. 
 | 
 */ 
 | 
#include <linux/dma-mapping.h> 
 | 
#include <linux/mei.h> 
 | 
  
 | 
#include "mei_dev.h" 
 | 
  
 | 
/** 
 | 
 * mei_dmam_dscr_alloc() - allocate a managed coherent buffer 
 | 
 *     for the dma descriptor 
 | 
 * @dev: mei_device 
 | 
 * @dscr: dma descriptor 
 | 
 * 
 | 
 * Return: 
 | 
 * * 0       - on success or zero allocation request 
 | 
 * * -EINVAL - if size is not power of 2 
 | 
 * * -ENOMEM - of allocation has failed 
 | 
 */ 
 | 
static int mei_dmam_dscr_alloc(struct mei_device *dev, 
 | 
                   struct mei_dma_dscr *dscr) 
 | 
{ 
 | 
    if (!dscr->size) 
 | 
        return 0; 
 | 
  
 | 
    if (WARN_ON(!is_power_of_2(dscr->size))) 
 | 
        return -EINVAL; 
 | 
  
 | 
    if (dscr->vaddr) 
 | 
        return 0; 
 | 
  
 | 
    dscr->vaddr = dmam_alloc_coherent(dev->dev, dscr->size, &dscr->daddr, 
 | 
                      GFP_KERNEL); 
 | 
    if (!dscr->vaddr) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * mei_dmam_dscr_free() - free a managed coherent buffer 
 | 
 *     from the dma descriptor 
 | 
 * @dev: mei_device 
 | 
 * @dscr: dma descriptor 
 | 
 */ 
 | 
static void mei_dmam_dscr_free(struct mei_device *dev, 
 | 
                   struct mei_dma_dscr *dscr) 
 | 
{ 
 | 
    if (!dscr->vaddr) 
 | 
        return; 
 | 
  
 | 
    dmam_free_coherent(dev->dev, dscr->size, dscr->vaddr, dscr->daddr); 
 | 
    dscr->vaddr = NULL; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * mei_dmam_ring_free() - free dma ring buffers 
 | 
 * @dev: mei device 
 | 
 */ 
 | 
void mei_dmam_ring_free(struct mei_device *dev) 
 | 
{ 
 | 
    int i; 
 | 
  
 | 
    for (i = 0; i < DMA_DSCR_NUM; i++) 
 | 
        mei_dmam_dscr_free(dev, &dev->dr_dscr[i]); 
 | 
} 
 | 
  
 | 
/** 
 | 
 * mei_dmam_ring_alloc() - allocate dma ring buffers 
 | 
 * @dev: mei device 
 | 
 * 
 | 
 * Return: -ENOMEM on allocation failure 0 otherwise 
 | 
 */ 
 | 
int mei_dmam_ring_alloc(struct mei_device *dev) 
 | 
{ 
 | 
    int i; 
 | 
  
 | 
    for (i = 0; i < DMA_DSCR_NUM; i++) 
 | 
        if (mei_dmam_dscr_alloc(dev, &dev->dr_dscr[i])) 
 | 
            goto err; 
 | 
  
 | 
    return 0; 
 | 
  
 | 
err: 
 | 
    mei_dmam_ring_free(dev); 
 | 
    return -ENOMEM; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * mei_dma_ring_is_allocated() - check if dma ring is allocated 
 | 
 * @dev: mei device 
 | 
 * 
 | 
 * Return: true if dma ring is allocated 
 | 
 */ 
 | 
bool mei_dma_ring_is_allocated(struct mei_device *dev) 
 | 
{ 
 | 
    return !!dev->dr_dscr[DMA_DSCR_HOST].vaddr; 
 | 
} 
 | 
  
 | 
static inline 
 | 
struct hbm_dma_ring_ctrl *mei_dma_ring_ctrl(struct mei_device *dev) 
 | 
{ 
 | 
    return (struct hbm_dma_ring_ctrl *)dev->dr_dscr[DMA_DSCR_CTRL].vaddr; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * mei_dma_ring_reset() - reset the dma control block 
 | 
 * @dev: mei device 
 | 
 */ 
 | 
void mei_dma_ring_reset(struct mei_device *dev) 
 | 
{ 
 | 
    struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev); 
 | 
  
 | 
    if (!ctrl) 
 | 
        return; 
 | 
  
 | 
    memset(ctrl, 0, sizeof(*ctrl)); 
 | 
} 
 | 
  
 | 
/** 
 | 
 * mei_dma_copy_from() - copy from dma ring into buffer 
 | 
 * @dev: mei device 
 | 
 * @buf: data buffer 
 | 
 * @offset: offset in slots. 
 | 
 * @n: number of slots to copy. 
 | 
 */ 
 | 
static size_t mei_dma_copy_from(struct mei_device *dev, unsigned char *buf, 
 | 
                u32 offset, u32 n) 
 | 
{ 
 | 
    unsigned char *dbuf = dev->dr_dscr[DMA_DSCR_DEVICE].vaddr; 
 | 
  
 | 
    size_t b_offset = offset << 2; 
 | 
    size_t b_n = n << 2; 
 | 
  
 | 
    memcpy(buf, dbuf + b_offset, b_n); 
 | 
  
 | 
    return b_n; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * mei_dma_copy_to() - copy to a buffer to the dma ring 
 | 
 * @dev: mei device 
 | 
 * @buf: data buffer 
 | 
 * @offset: offset in slots. 
 | 
 * @n: number of slots to copy. 
 | 
 */ 
 | 
static size_t mei_dma_copy_to(struct mei_device *dev, unsigned char *buf, 
 | 
                  u32 offset, u32 n) 
 | 
{ 
 | 
    unsigned char *hbuf = dev->dr_dscr[DMA_DSCR_HOST].vaddr; 
 | 
  
 | 
    size_t b_offset = offset << 2; 
 | 
    size_t b_n = n << 2; 
 | 
  
 | 
    memcpy(hbuf + b_offset, buf, b_n); 
 | 
  
 | 
    return b_n; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * mei_dma_ring_read() - read data from the ring 
 | 
 * @dev: mei device 
 | 
 * @buf: buffer to read into: may be NULL in case of droping the data. 
 | 
 * @len: length to read. 
 | 
 */ 
 | 
void mei_dma_ring_read(struct mei_device *dev, unsigned char *buf, u32 len) 
 | 
{ 
 | 
    struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev); 
 | 
    u32 dbuf_depth; 
 | 
    u32 rd_idx, rem, slots; 
 | 
  
 | 
    if (WARN_ON(!ctrl)) 
 | 
        return; 
 | 
  
 | 
    dev_dbg(dev->dev, "reading from dma %u bytes\n", len); 
 | 
  
 | 
    if (!len) 
 | 
        return; 
 | 
  
 | 
    dbuf_depth = dev->dr_dscr[DMA_DSCR_DEVICE].size >> 2; 
 | 
    rd_idx = READ_ONCE(ctrl->dbuf_rd_idx) & (dbuf_depth - 1); 
 | 
    slots = mei_data2slots(len); 
 | 
  
 | 
    /* if buf is NULL we drop the packet by advancing the pointer.*/ 
 | 
    if (!buf) 
 | 
        goto out; 
 | 
  
 | 
    if (rd_idx + slots > dbuf_depth) { 
 | 
        buf += mei_dma_copy_from(dev, buf, rd_idx, dbuf_depth - rd_idx); 
 | 
        rem = slots - (dbuf_depth - rd_idx); 
 | 
        rd_idx = 0; 
 | 
    } else { 
 | 
        rem = slots; 
 | 
    } 
 | 
  
 | 
    mei_dma_copy_from(dev, buf, rd_idx, rem); 
 | 
out: 
 | 
    WRITE_ONCE(ctrl->dbuf_rd_idx, ctrl->dbuf_rd_idx + slots); 
 | 
} 
 | 
  
 | 
static inline u32 mei_dma_ring_hbuf_depth(struct mei_device *dev) 
 | 
{ 
 | 
    return dev->dr_dscr[DMA_DSCR_HOST].size >> 2; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * mei_dma_ring_empty_slots() - calaculate number of empty slots in dma ring 
 | 
 * @dev: mei_device 
 | 
 * 
 | 
 * Return: number of empty slots 
 | 
 */ 
 | 
u32 mei_dma_ring_empty_slots(struct mei_device *dev) 
 | 
{ 
 | 
    struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev); 
 | 
    u32 wr_idx, rd_idx, hbuf_depth, empty; 
 | 
  
 | 
    if (!mei_dma_ring_is_allocated(dev)) 
 | 
        return 0; 
 | 
  
 | 
    if (WARN_ON(!ctrl)) 
 | 
        return 0; 
 | 
  
 | 
    /* easier to work in slots */ 
 | 
    hbuf_depth = mei_dma_ring_hbuf_depth(dev); 
 | 
    rd_idx = READ_ONCE(ctrl->hbuf_rd_idx); 
 | 
    wr_idx = READ_ONCE(ctrl->hbuf_wr_idx); 
 | 
  
 | 
    if (rd_idx > wr_idx) 
 | 
        empty = rd_idx - wr_idx; 
 | 
    else 
 | 
        empty = hbuf_depth - (wr_idx - rd_idx); 
 | 
  
 | 
    return empty; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * mei_dma_ring_write - write data to dma ring host buffer 
 | 
 * 
 | 
 * @dev: mei_device 
 | 
 * @buf: data will be written 
 | 
 * @len: data length 
 | 
 */ 
 | 
void mei_dma_ring_write(struct mei_device *dev, unsigned char *buf, u32 len) 
 | 
{ 
 | 
    struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev); 
 | 
    u32 hbuf_depth; 
 | 
    u32 wr_idx, rem, slots; 
 | 
  
 | 
    if (WARN_ON(!ctrl)) 
 | 
        return; 
 | 
  
 | 
    dev_dbg(dev->dev, "writing to dma %u bytes\n", len); 
 | 
    hbuf_depth = mei_dma_ring_hbuf_depth(dev); 
 | 
    wr_idx = READ_ONCE(ctrl->hbuf_wr_idx) & (hbuf_depth - 1); 
 | 
    slots = mei_data2slots(len); 
 | 
  
 | 
    if (wr_idx + slots > hbuf_depth) { 
 | 
        buf += mei_dma_copy_to(dev, buf, wr_idx, hbuf_depth - wr_idx); 
 | 
        rem = slots - (hbuf_depth - wr_idx); 
 | 
        wr_idx = 0; 
 | 
    } else { 
 | 
        rem = slots; 
 | 
    } 
 | 
  
 | 
    mei_dma_copy_to(dev, buf, wr_idx, rem); 
 | 
  
 | 
    WRITE_ONCE(ctrl->hbuf_wr_idx, ctrl->hbuf_wr_idx + slots); 
 | 
} 
 |