Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/hid/intel-thc-hid/intel-thc/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 29 kB image not shown  

Quelle  intel-thc-dma.c   Sprache: C

 
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2024 Intel Corporation */

#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/overflow.h>
#include <linux/regmap.h>
#include <linux/scatterlist.h>

#include "intel-thc-dev.h"
#include "intel-thc-dma.h"
#include "intel-thc-hw.h"

static void dma_set_prd_base_addr(struct thc_device *dev, u64 physical_addr,
      struct thc_dma_configuration *dma_config)
{
 u32 addr_high, addr_low;

 if (!dma_config->is_enabled)
  return;

 addr_high = upper_32_bits(physical_addr);
 addr_low = lower_32_bits(physical_addr);

 regmap_write(dev->thc_regmap, dma_config->prd_base_addr_high, addr_high);
 regmap_write(dev->thc_regmap, dma_config->prd_base_addr_low, addr_low);
}

static void dma_set_start_bit(struct thc_device *dev,
         struct thc_dma_configuration *dma_config)
{
 u32 ctrl, mask, mbits, data, offset;

 if (!dma_config->is_enabled)
  return;

 switch (dma_config->dma_channel) {
 case THC_RXDMA1:
 case THC_RXDMA2:
  if (dma_config->dma_channel == THC_RXDMA2) {
   mbits = FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL,
        THC_BITMASK_INTERRUPT_TYPE_DATA);
   mask = THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL;
   regmap_write_bits(dev->thc_regmap,
       THC_M_PRT_DEVINT_CFG_1_OFFSET, mask, mbits);
  }

  mbits = THC_M_PRT_READ_DMA_CNTRL_IE_EOF |
   THC_M_PRT_READ_DMA_CNTRL_SOO |
   THC_M_PRT_READ_DMA_CNTRL_IE_STALL |
   THC_M_PRT_READ_DMA_CNTRL_IE_ERROR |
   THC_M_PRT_READ_DMA_CNTRL_START;

  mask = THC_M_PRT_READ_DMA_CNTRL_TPCWP | mbits;
  mask |= THC_M_PRT_READ_DMA_CNTRL_INT_SW_DMA_EN;
  ctrl = FIELD_PREP(THC_M_PRT_READ_DMA_CNTRL_TPCWP, THC_POINTER_WRAPAROUND) | mbits;
  offset = dma_config->dma_channel == THC_RXDMA1 ?
    THC_M_PRT_READ_DMA_CNTRL_1_OFFSET : THC_M_PRT_READ_DMA_CNTRL_2_OFFSET;
  regmap_write_bits(dev->thc_regmap, offset, mask, ctrl);
  break;

 case THC_SWDMA:
  mbits = THC_M_PRT_READ_DMA_CNTRL_IE_DMACPL |
   THC_M_PRT_READ_DMA_CNTRL_IE_IOC |
   THC_M_PRT_READ_DMA_CNTRL_SOO |
   THC_M_PRT_READ_DMA_CNTRL_START;

  mask = THC_M_PRT_READ_DMA_CNTRL_TPCWP | mbits;
  ctrl = FIELD_PREP(THC_M_PRT_READ_DMA_CNTRL_TPCWP, THC_POINTER_WRAPAROUND) | mbits;
  regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET,
      mask, ctrl);
  break;

 case THC_TXDMA:
  regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_INT_STS_OFFSET,
      THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS,
      THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS);

  /* Select interrupt or polling method upon Write completion */
  if (dev->dma_ctx->use_write_interrupts)
   data = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL;
  else
   data = 0;

  data |= THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_START;
  mask = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL |
         THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_START;
  regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_DMA_CNTRL_OFFSET,
      mask, data);
  break;

 default:
  break;
 }
}

static void dma_set_prd_control(struct thc_device *dev, u8 entry_count, u8 cb_depth,
    struct thc_dma_configuration *dma_config)
{
 u32 ctrl, mask;

 if (!dma_config->is_enabled)
  return;

 if (dma_config->dma_channel == THC_TXDMA) {
  mask = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_PTEC;
  ctrl = FIELD_PREP(THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_PTEC, entry_count);
 } else {
  mask = THC_M_PRT_RPRD_CNTRL_PTEC | THC_M_PRT_RPRD_CNTRL_PCD;
  ctrl = FIELD_PREP(THC_M_PRT_RPRD_CNTRL_PTEC, entry_count) |
         FIELD_PREP(THC_M_PRT_RPRD_CNTRL_PCD, cb_depth);
 }

 regmap_write_bits(dev->thc_regmap, dma_config->prd_cntrl, mask, ctrl);
}

static void dma_clear_prd_control(struct thc_device *dev,
      struct thc_dma_configuration *dma_config)
{
 u32 mask;

 if (!dma_config->is_enabled)
  return;

 if (dma_config->dma_channel == THC_TXDMA)
  mask = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_PTEC;
 else
  mask = THC_M_PRT_RPRD_CNTRL_PTEC | THC_M_PRT_RPRD_CNTRL_PCD;

 regmap_write_bits(dev->thc_regmap, dma_config->prd_cntrl, mask, 0);
}

static u8 dma_get_read_pointer(struct thc_device *dev,
          struct thc_dma_configuration *dma_config)
{
 u32 ctrl, read_pointer;

 regmap_read(dev->thc_regmap, dma_config->dma_cntrl, &ctrl);
 read_pointer = FIELD_GET(THC_M_PRT_READ_DMA_CNTRL_TPCRP, ctrl);

 dev_dbg(dev->dev, "THC_M_PRT_READ_DMA_CNTRL 0x%x offset 0x%x TPCRP 0x%x\n",
  ctrl, dma_config->dma_cntrl, read_pointer);

 return read_pointer;
}

static u8 dma_get_write_pointer(struct thc_device *dev,
    struct thc_dma_configuration *dma_config)
{
 u32 ctrl, write_pointer;

 regmap_read(dev->thc_regmap, dma_config->dma_cntrl, &ctrl);
 write_pointer = FIELD_GET(THC_M_PRT_READ_DMA_CNTRL_TPCWP, ctrl);

 dev_dbg(dev->dev, "THC_M_PRT_READ_DMA_CNTRL 0x%x offset 0x%x TPCWP 0x%x\n",
  ctrl, dma_config->dma_cntrl, write_pointer);

 return write_pointer;
}

static void dma_set_write_pointer(struct thc_device *dev, u8 value,
      struct thc_dma_configuration *dma_config)
{
 u32 ctrl, mask;

 mask = THC_M_PRT_READ_DMA_CNTRL_TPCWP;
 ctrl = FIELD_PREP(THC_M_PRT_READ_DMA_CNTRL_TPCWP, value);
 regmap_write_bits(dev->thc_regmap, dma_config->dma_cntrl, mask, ctrl);
}

static size_t dma_get_max_packet_size(struct thc_device *dev,
          struct thc_dma_configuration *dma_config)
{
 return dma_config->max_packet_size;
}

static void dma_set_max_packet_size(struct thc_device *dev, size_t size,
        struct thc_dma_configuration *dma_config)
{
 if (size) {
  dma_config->max_packet_size = ALIGN(size, SZ_4K);
  dma_config->is_enabled = true;
 }
}

static void thc_copy_one_sgl_to_prd(struct thc_device *dev,
        struct thc_dma_configuration *config,
        unsigned int ind)
{
 struct thc_prd_table *prd_tbl;
 struct scatterlist *sg;
 int j;

 prd_tbl = &config->prd_tbls[ind];

 for_each_sg(config->sgls[ind], sg, config->sgls_nent[ind], j) {
  prd_tbl->entries[j].dest_addr =
    sg_dma_address(sg) >> THC_ADDRESS_SHIFT;
  prd_tbl->entries[j].len = sg_dma_len(sg);
  prd_tbl->entries[j].hw_status = 0;
  prd_tbl->entries[j].end_of_prd = 0;
 }

 /* Set the end_of_prd flag in the last filled entry */
 if (j > 0)
  prd_tbl->entries[j - 1].end_of_prd = 1;
}

static void thc_copy_sgls_to_prd(struct thc_device *dev,
     struct thc_dma_configuration *config)
{
 unsigned int i;

 memset(config->prd_tbls, 0, array_size(PRD_TABLE_SIZE, config->prd_tbl_num));

 for (i = 0; i < config->prd_tbl_num; i++)
  thc_copy_one_sgl_to_prd(dev, config, i);
}

static int setup_dma_buffers(struct thc_device *dev,
        struct thc_dma_configuration *config,
        enum dma_data_direction dir)
{
 size_t prd_tbls_size = array_size(PRD_TABLE_SIZE, config->prd_tbl_num);
 unsigned int i, nent = PRD_ENTRIES_NUM;
 dma_addr_t dma_handle;
 void *cpu_addr;
 size_t buf_sz;
 int count;

 if (!config->is_enabled)
  return 0;

 memset(config->sgls, 0, sizeof(config->sgls));
 memset(config->sgls_nent, 0, sizeof(config->sgls_nent));

 cpu_addr = dma_alloc_coherent(dev->dev, prd_tbls_size,
          &dma_handle, GFP_KERNEL);
 if (!cpu_addr)
  return -ENOMEM;

 config->prd_tbls = cpu_addr;
 config->prd_tbls_dma_handle = dma_handle;

 buf_sz = dma_get_max_packet_size(dev, config);

 /* Allocate and map the scatter-gather lists, one for each PRD table */
 for (i = 0; i < config->prd_tbl_num; i++) {
  config->sgls[i] = sgl_alloc(buf_sz, GFP_KERNEL, &nent);
  if (!config->sgls[i] || nent > PRD_ENTRIES_NUM) {
   dev_err_once(dev->dev, "sgl_alloc (%uth) failed, nent %u\n",
         i, nent);
   return -ENOMEM;
  }
  count = dma_map_sg(dev->dev, config->sgls[i], nent, dir);

  config->sgls_nent[i] = count;
 }

 thc_copy_sgls_to_prd(dev, config);

 return 0;
}

static void thc_reset_dma_settings(struct thc_device *dev)
{
 /* Stop all DMA channels and reset DMA read pointers */
 regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
     THC_M_PRT_READ_DMA_CNTRL_START, 0);
 regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET,
     THC_M_PRT_READ_DMA_CNTRL_START, 0);
 regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET,
     THC_M_PRT_READ_DMA_CNTRL_START, 0);
 regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_DMA_CNTRL_OFFSET,
     THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_START, 0);

 regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
     THC_M_PRT_READ_DMA_CNTRL_TPCPR,
     THC_M_PRT_READ_DMA_CNTRL_TPCPR);
 regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET,
     THC_M_PRT_READ_DMA_CNTRL_TPCPR,
     THC_M_PRT_READ_DMA_CNTRL_TPCPR);
 regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET,
     THC_M_PRT_READ_DMA_CNTRL_TPCPR,
     THC_M_PRT_READ_DMA_CNTRL_TPCPR);
}

static void release_dma_buffers(struct thc_device *dev,
    struct thc_dma_configuration *config)
{
 size_t prd_tbls_size = array_size(PRD_TABLE_SIZE, config->prd_tbl_num);
 unsigned int i;

 if (!config->is_enabled)
  return;

 for (i = 0; i < config->prd_tbl_num; i++) {
  if (!config->sgls[i] || !config->sgls_nent[i])
   continue;

  dma_unmap_sg(dev->dev, config->sgls[i],
        config->sgls_nent[i],
        config->dir);

  sgl_free(config->sgls[i]);
  config->sgls[i] = NULL;
 }

 memset(config->prd_tbls, 0, prd_tbls_size);

 if (config->prd_tbls) {
  dma_free_coherent(dev->dev, prd_tbls_size, config->prd_tbls,
      config->prd_tbls_dma_handle);
  config->prd_tbls = NULL;
  config->prd_tbls_dma_handle = 0;
 }
}

struct thc_dma_context *thc_dma_init(struct thc_device *dev)
{
 struct thc_dma_context *dma_ctx;

 dma_ctx = devm_kzalloc(dev->dev, sizeof(*dma_ctx), GFP_KERNEL);
 if (!dma_ctx)
  return NULL;

 dev->dma_ctx = dma_ctx;

 dma_ctx->dma_config[THC_RXDMA1].dma_channel = THC_RXDMA1;
 dma_ctx->dma_config[THC_RXDMA2].dma_channel = THC_RXDMA2;
 dma_ctx->dma_config[THC_TXDMA].dma_channel = THC_TXDMA;
 dma_ctx->dma_config[THC_SWDMA].dma_channel = THC_SWDMA;

 dma_ctx->dma_config[THC_RXDMA1].dir = DMA_FROM_DEVICE;
 dma_ctx->dma_config[THC_RXDMA2].dir = DMA_FROM_DEVICE;
 dma_ctx->dma_config[THC_TXDMA].dir = DMA_TO_DEVICE;
 dma_ctx->dma_config[THC_SWDMA].dir = DMA_FROM_DEVICE;

 dma_ctx->dma_config[THC_RXDMA1].prd_tbl_num = PRD_TABLES_NUM;
 dma_ctx->dma_config[THC_RXDMA2].prd_tbl_num = PRD_TABLES_NUM;
 dma_ctx->dma_config[THC_TXDMA].prd_tbl_num = 1;
 dma_ctx->dma_config[THC_SWDMA].prd_tbl_num = 1;

 dma_ctx->dma_config[THC_RXDMA1].prd_base_addr_high = THC_M_PRT_RPRD_BA_HI_1_OFFSET;
 dma_ctx->dma_config[THC_RXDMA2].prd_base_addr_high = THC_M_PRT_RPRD_BA_HI_2_OFFSET;
 dma_ctx->dma_config[THC_TXDMA].prd_base_addr_high = THC_M_PRT_WPRD_BA_HI_OFFSET;
 dma_ctx->dma_config[THC_SWDMA].prd_base_addr_high = THC_M_PRT_RPRD_BA_HI_SW_OFFSET;

 dma_ctx->dma_config[THC_RXDMA1].prd_base_addr_low = THC_M_PRT_RPRD_BA_LOW_1_OFFSET;
 dma_ctx->dma_config[THC_RXDMA2].prd_base_addr_low = THC_M_PRT_RPRD_BA_LOW_2_OFFSET;
 dma_ctx->dma_config[THC_TXDMA].prd_base_addr_low = THC_M_PRT_WPRD_BA_LOW_OFFSET;
 dma_ctx->dma_config[THC_SWDMA].prd_base_addr_low = THC_M_PRT_RPRD_BA_LOW_SW_OFFSET;

 dma_ctx->dma_config[THC_RXDMA1].prd_cntrl = THC_M_PRT_RPRD_CNTRL_1_OFFSET;
 dma_ctx->dma_config[THC_RXDMA2].prd_cntrl = THC_M_PRT_RPRD_CNTRL_2_OFFSET;
 dma_ctx->dma_config[THC_TXDMA].prd_cntrl = THC_M_PRT_WRITE_DMA_CNTRL_OFFSET;
 dma_ctx->dma_config[THC_SWDMA].prd_cntrl = THC_M_PRT_RPRD_CNTRL_SW_OFFSET;

 dma_ctx->dma_config[THC_RXDMA1].dma_cntrl = THC_M_PRT_READ_DMA_CNTRL_1_OFFSET;
 dma_ctx->dma_config[THC_RXDMA2].dma_cntrl = THC_M_PRT_READ_DMA_CNTRL_2_OFFSET;
 dma_ctx->dma_config[THC_TXDMA].dma_cntrl = THC_M_PRT_WRITE_DMA_CNTRL_OFFSET;
 dma_ctx->dma_config[THC_SWDMA].dma_cntrl = THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET;

 /* Enable write DMA completion interrupt by default */
 dma_ctx->use_write_interrupts = 1;

 return dma_ctx;
}

/**
 * thc_dma_set_max_packet_sizes - Set max packet sizes for all DMA engines
 *
 * @dev: The pointer of THC private device context
 * @mps_read1: RxDMA1 max packet size
 * @mps_read2: RxDMA2 max packet size
 * @mps_write: TxDMA max packet size
 * @mps_swdma: Software DMA max packet size
 *
 * If mps is not 0, it means the corresponding DMA channel is used, then set
 * the flag to turn on this channel.
 *
 * Return: 0 on success, other error codes on failed.
 */

int thc_dma_set_max_packet_sizes(struct thc_device *dev, size_t mps_read1,
     size_t mps_read2, size_t mps_write,
     size_t mps_swdma)
{
 if (!dev->dma_ctx) {
  dev_err_once(dev->dev,
        "Cannot set max packet sizes because DMA context is NULL!\n");
  return -EINVAL;
 }

 dma_set_max_packet_size(dev, mps_read1, &dev->dma_ctx->dma_config[THC_RXDMA1]);
 dma_set_max_packet_size(dev, mps_read2, &dev->dma_ctx->dma_config[THC_RXDMA2]);
 dma_set_max_packet_size(dev, mps_write, &dev->dma_ctx->dma_config[THC_TXDMA]);
 dma_set_max_packet_size(dev, mps_swdma, &dev->dma_ctx->dma_config[THC_SWDMA]);

 return 0;
}
EXPORT_SYMBOL_NS_GPL(thc_dma_set_max_packet_sizes, "INTEL_THC");

/**
 * thc_dma_allocate - Allocate DMA buffers for all DMA engines
 *
 * @dev: The pointer of THC private device context
 *
 * Return: 0 on success, other error codes on failed.
 */

int thc_dma_allocate(struct thc_device *dev)
{
 int ret, chan;

 for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++) {
  ret = setup_dma_buffers(dev, &dev->dma_ctx->dma_config[chan],
     dev->dma_ctx->dma_config[chan].dir);
  if (ret < 0) {
   dev_err_once(dev->dev, "DMA setup failed for DMA channel %d\n", chan);
   goto release_bufs;
  }
 }

 return 0;

release_bufs:
 while (chan--)
  release_dma_buffers(dev, &dev->dma_ctx->dma_config[chan]);

 return ret;
}
EXPORT_SYMBOL_NS_GPL(thc_dma_allocate, "INTEL_THC");

/**
 * thc_dma_release - Release DMA buffers for all DMA engines
 *
 * @dev: The pointer of THC private device context
 */

void thc_dma_release(struct thc_device *dev)
{
 int chan;

 for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++)
  release_dma_buffers(dev, &dev->dma_ctx->dma_config[chan]);
}
EXPORT_SYMBOL_NS_GPL(thc_dma_release, "INTEL_THC");

static int calc_prd_entries_num(struct thc_prd_table *prd_tbl,
    size_t mes_len, u8 *nent)
{
 *nent = DIV_ROUND_UP(mes_len, THC_MIN_BYTES_PER_SG_LIST_ENTRY);
 if (*nent > PRD_ENTRIES_NUM)
  return -EMSGSIZE;

 return 0;
}

static size_t calc_message_len(struct thc_prd_table *prd_tbl, u8 *nent)
{
 size_t mes_len = 0;
 unsigned int j;

 for (j = 0; j < PRD_ENTRIES_NUM; j++) {
  mes_len += prd_tbl->entries[j].len;
  if (prd_tbl->entries[j].end_of_prd)
   break;
 }

 *nent = j + 1;

 return mes_len;
}

/**
 * thc_dma_configure - Configure DMA settings for all DMA engines
 *
 * @dev: The pointer of THC private device context
 *
 * Return: 0 on success, other error codes on failed.
 */

int thc_dma_configure(struct thc_device *dev)
{
 struct thc_dma_context *dma_ctx = dev->dma_ctx;
 int chan;

 thc_reset_dma_settings(dev);

 if (!dma_ctx) {
  dev_err_once(dev->dev, "Cannot do DMA configure because DMA context is NULL\n");
  return -EINVAL;
 }

 for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++) {
  dma_set_prd_base_addr(dev,
          dma_ctx->dma_config[chan].prd_tbls_dma_handle,
          &dma_ctx->dma_config[chan]);

  dma_set_prd_control(dev, PRD_ENTRIES_NUM - 1,
        dma_ctx->dma_config[chan].prd_tbl_num - 1,
        &dma_ctx->dma_config[chan]);
 }

 /* Start read2 DMA engine */
 dma_set_start_bit(dev, &dma_ctx->dma_config[THC_RXDMA2]);

 dev_dbg(dev->dev, "DMA configured successfully!\n");

 return 0;
}
EXPORT_SYMBOL_NS_GPL(thc_dma_configure, "INTEL_THC");

/**
 * thc_dma_unconfigure - Unconfigure DMA settings for all DMA engines
 *
 * @dev: The pointer of THC private device context
 */

void thc_dma_unconfigure(struct thc_device *dev)
{
 int chan;

 for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++) {
  dma_set_prd_base_addr(dev, 0, &dev->dma_ctx->dma_config[chan]);
  dma_clear_prd_control(dev, &dev->dma_ctx->dma_config[chan]);
 }

 regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
     THC_M_PRT_READ_DMA_CNTRL_START, 0);

 regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET,
     THC_M_PRT_READ_DMA_CNTRL_START, 0);
}
EXPORT_SYMBOL_NS_GPL(thc_dma_unconfigure, "INTEL_THC");

static int thc_wait_for_dma_pause(struct thc_device *dev, enum thc_dma_channel channel)
{
 u32 ctrl_reg, sts_reg, sts;
 int ret;

 ctrl_reg = (channel == THC_RXDMA1) ? THC_M_PRT_READ_DMA_CNTRL_1_OFFSET :
   ((channel == THC_RXDMA2) ? THC_M_PRT_READ_DMA_CNTRL_2_OFFSET :
         THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET);

 regmap_write_bits(dev->thc_regmap, ctrl_reg, THC_M_PRT_READ_DMA_CNTRL_START, 0);

 sts_reg = (channel == THC_RXDMA1) ? THC_M_PRT_READ_DMA_INT_STS_1_OFFSET :
   ((channel == THC_RXDMA2) ? THC_M_PRT_READ_DMA_INT_STS_2_OFFSET :
         THC_M_PRT_READ_DMA_INT_STS_SW_OFFSET);

 ret = regmap_read_poll_timeout(dev->thc_regmap, sts_reg, sts,
           !(sts & THC_M_PRT_READ_DMA_INT_STS_ACTIVE),
           THC_DEFAULT_RXDMA_POLLING_US_INTERVAL,
           THC_DEFAULT_RXDMA_POLLING_US_TIMEOUT);

 if (ret) {
  dev_err_once(dev->dev,
        "Timeout while waiting for DMA %d stop\n", channel);
  return ret;
 }

 return 0;
}

static int read_dma_buffer(struct thc_device *dev,
      struct thc_dma_configuration *read_config,
      u8 prd_table_index, void *read_buff)
{
 struct thc_prd_table *prd_tbl;
 struct scatterlist *sg;
 size_t mes_len, ret;
 u8 nent;

 if (prd_table_index >= read_config->prd_tbl_num) {
  dev_err_once(dev->dev, "PRD table index %d too big\n", prd_table_index);
  return -EINVAL;
 }

 prd_tbl = &read_config->prd_tbls[prd_table_index];
 mes_len = calc_message_len(prd_tbl, &nent);
 if (mes_len > read_config->max_packet_size) {
  dev_err(dev->dev,
   "Message length %zu is bigger than buffer length %lu\n",
   mes_len, read_config->max_packet_size);
  return -EMSGSIZE;
 }

 sg = read_config->sgls[prd_table_index];
 ret = sg_copy_to_buffer(sg, nent, read_buff, mes_len);
 if (ret != mes_len) {
  dev_err_once(dev->dev, "Copied %zu bytes instead of requested %zu\n",
        ret, mes_len);
  return -EIO;
 }

 return mes_len;
}

static void update_write_pointer(struct thc_device *dev,
     struct thc_dma_configuration *read_config)
{
 u8 write_ptr = dma_get_write_pointer(dev, read_config);

 if (write_ptr + 1 == THC_WRAPAROUND_VALUE_ODD)
  dma_set_write_pointer(dev, THC_POINTER_WRAPAROUND, read_config);
 else if (write_ptr + 1 == THC_WRAPAROUND_VALUE_EVEN)
  dma_set_write_pointer(dev, 0, read_config);
 else
  dma_set_write_pointer(dev, write_ptr + 1, read_config);
}

static int is_dma_buf_empty(struct thc_device *dev,
       struct thc_dma_configuration *read_config,
       u8 *read_ptr, u8 *write_ptr)
{
 *read_ptr = dma_get_read_pointer(dev, read_config);
 *write_ptr = dma_get_write_pointer(dev, read_config);

 if ((*read_ptr & THC_POINTER_MASK) == (*write_ptr & THC_POINTER_MASK))
  if (*read_ptr != *write_ptr)
   return true;

 return false;
}

static int thc_dma_read(struct thc_device *dev,
   struct thc_dma_configuration *read_config,
   void *read_buff, size_t *read_len, int *read_finished)
{
 u8 read_ptr, write_ptr, prd_table_index;
 int status;

 if (!is_dma_buf_empty(dev, read_config, &read_ptr, &write_ptr)) {
  prd_table_index = write_ptr & THC_POINTER_MASK;

  status = read_dma_buffer(dev, read_config, prd_table_index, read_buff);
  if (status <= 0) {
   dev_err_once(dev->dev, "read DMA buffer failed %d\n", status);
   return -EIO;
  }

  *read_len = status;

  /* Clear the relevant PRD table */
  thc_copy_one_sgl_to_prd(dev, read_config, prd_table_index);

  /* Increment the write pointer to let the HW know we have processed this PRD */
  update_write_pointer(dev, read_config);
 }

 /*
 * This function only reads one frame from PRD table for each call, so we need to
 * check if all DMAed data is read out and return the flag to the caller. Caller
 * should repeatedly call thc_dma_read() until all DMAed data is handled.
 */

 if (read_finished)
  *read_finished = is_dma_buf_empty(dev, read_config, &read_ptr, &write_ptr) ? 1 : 0;

 return 0;
}

/**
 * thc_rxdma_read - Read data from RXDMA buffer
 *
 * @dev: The pointer of THC private device context
 * @dma_channel: The RXDMA engine of read data source
 * @read_buff: The pointer of the read data buffer
 * @read_len: The pointer of the read data length
 * @read_finished: The pointer of the flag indicating if all pending data has been read out
 *
 * Return: 0 on success, other error codes on failed.
 */

int thc_rxdma_read(struct thc_device *dev, enum thc_dma_channel dma_channel,
     void *read_buff, size_t *read_len, int *read_finished)
{
 struct thc_dma_configuration *dma_config;
 int ret;

 dma_config = &dev->dma_ctx->dma_config[dma_channel];

 if (!dma_config->is_enabled) {
  dev_err_once(dev->dev, "The DMA channel %d is not enabled", dma_channel);
  return -EINVAL;
 }

 if (!read_buff || !read_len) {
  dev_err(dev->dev, "Invalid input parameters, read_buff %p, read_len %p\n",
   read_buff, read_len);
  return -EINVAL;
 }

 if (dma_channel >= THC_TXDMA) {
  dev_err(dev->dev, "Unsupported DMA channel for RxDMA read, %d\n", dma_channel);
  return -EINVAL;
 }

 ret = thc_dma_read(dev, dma_config, read_buff, read_len, read_finished);

 return ret;
}
EXPORT_SYMBOL_NS_GPL(thc_rxdma_read, "INTEL_THC");

static int thc_swdma_read_start(struct thc_device *dev, void *write_buff,
    size_t write_len, u32 *prd_tbl_len)
{
 u32 mask, val, data0 = 0, data1 = 0;
 int ret;

 ret = thc_interrupt_quiesce(dev, true);
 if (ret)
  return ret;

 if (thc_wait_for_dma_pause(dev, THC_RXDMA1) || thc_wait_for_dma_pause(dev, THC_RXDMA2))
  return -EIO;

 thc_reset_dma_settings(dev);

 /*
 * Max input size control feature is only available for RxDMA, it must keep disabled
 * during SWDMA operation, and restore to previous state after SWDMA is done.
 * Max input size variables in THC device context track hardware state, and keep change
 * when feature state was changed, so those variables cannot be used to record feature
 * state after state was changed during SWDMA operation. Here have to use a temp variable
 * in DMA context to record feature state before SWDMA operation.
 */

 if (dev->i2c_max_rx_size_en) {
  thc_i2c_rx_max_size_enable(dev, false);
  dev->dma_ctx->rx_max_size_en = true;
 }

 /*
 * Interrupt delay feature is in the same situation with max input size control feature,
 * needs record feature state before SWDMA.
 */

 if (dev->i2c_int_delay_en) {
  thc_i2c_rx_int_delay_enable(dev, false);
  dev->dma_ctx->rx_int_delay_en = true;
 }

 mask = THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_WBC |
        THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_RX_DLEN_EN;
 val = FIELD_PREP(THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_WBC, write_len) |
       ((!prd_tbl_len) ? THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_RX_DLEN_EN : 0);
 regmap_write_bits(dev->thc_regmap, THC_M_PRT_RPRD_CNTRL_SW_OFFSET,
     mask, val);

 if (prd_tbl_len) {
  mask = THC_M_PRT_SW_DMA_PRD_TABLE_LEN_THC_M_PRT_SW_DMA_PRD_TABLE_LEN;
  val = FIELD_PREP(THC_M_PRT_SW_DMA_PRD_TABLE_LEN_THC_M_PRT_SW_DMA_PRD_TABLE_LEN,
     *prd_tbl_len);
  regmap_write_bits(dev->thc_regmap, THC_M_PRT_SW_DMA_PRD_TABLE_LEN_OFFSET,
      mask, val);
 }

 if (write_len <= sizeof(u32)) {
  for (int i = 0; i < write_len; i++)
   data0 |= *(((u8 *)write_buff) + i) << (i * 8);

  regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA0_ADDR_OFFSET, data0);
 } else if (write_len <= 2 * sizeof(u32)) {
  data0 = *(u32 *)write_buff;
  regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA0_ADDR_OFFSET, data0);

  for (int i = 0; i < write_len - sizeof(u32); i++)
   data1 |= *(((u8 *)write_buff) + sizeof(u32) + i) << (i * 8);

  regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA1_OFFSET, data1);
 }
 dma_set_start_bit(dev, &dev->dma_ctx->dma_config[THC_SWDMA]);

 return 0;
}

static int thc_swdma_read_completion(struct thc_device *dev)
{
 int ret;

 ret = thc_wait_for_dma_pause(dev, THC_SWDMA);
 if (ret)
  return ret;

 /*
 * Restore max input size control feature to previous state after SWDMA if it was
 * enabled before SWDMA, and reset temp rx_max_size_en variable for next time.
 */

 if (dev->dma_ctx->rx_max_size_en) {
  thc_i2c_rx_max_size_enable(dev, true);
  dev->dma_ctx->rx_max_size_en = false;
 }

 /*
 * Restore input interrupt delay feature to previous state after SWDMA if it was
 * enabled before SWDMA, and reset temp rx_int_delay_en variable for next time.
 */

 if (dev->dma_ctx->rx_int_delay_en) {
  thc_i2c_rx_int_delay_enable(dev, true);
  dev->dma_ctx->rx_int_delay_en = false;
 }

 thc_reset_dma_settings(dev);

 dma_set_start_bit(dev, &dev->dma_ctx->dma_config[THC_RXDMA2]);

 ret = thc_interrupt_quiesce(dev, false);

 return ret;
}

/**
 * thc_swdma_read - Use software DMA to read data from touch device
 *
 * @dev: The pointer of THC private device context
 * @write_buff: The pointer of write buffer for SWDMA sequence
 * @write_len: The write data length for SWDMA sequence
 * @prd_tbl_len: The prd table length of SWDMA engine, can be set to NULL
 * @read_buff: The pointer of the read data buffer
 * @read_len: The pointer of the read data length
 *
 * Return: 0 on success, other error codes on failed.
 */

int thc_swdma_read(struct thc_device *dev, void *write_buff, size_t write_len,
     u32 *prd_tbl_len, void *read_buff, size_t *read_len)
{
 int ret;

 if (!(&dev->dma_ctx->dma_config[THC_SWDMA])->is_enabled) {
  dev_err_once(dev->dev, "The SWDMA channel is not enabled");
  return -EINVAL;
 }

 if (!read_buff || !read_len) {
  dev_err(dev->dev, "Invalid input parameters, read_buff %p, read_len %p\n",
   read_buff, read_len);
  return -EINVAL;
 }

 if (mutex_lock_interruptible(&dev->thc_bus_lock))
  return -EINTR;

 dev->swdma_done = false;

 ret = thc_swdma_read_start(dev, write_buff, write_len, prd_tbl_len);
 if (ret)
  goto end;

 ret = wait_event_interruptible_timeout(dev->swdma_complete_wait, dev->swdma_done, 1 * HZ);
 if (ret <= 0 || !dev->swdma_done) {
  dev_err_once(dev->dev, "timeout for waiting SWDMA completion\n");
  ret = -ETIMEDOUT;
  goto end;
 }

 ret = thc_dma_read(dev, &dev->dma_ctx->dma_config[THC_SWDMA], read_buff, read_len, NULL);
 if (ret)
  goto end;

 ret = thc_swdma_read_completion(dev);

end:
 mutex_unlock(&dev->thc_bus_lock);
 return ret;
}
EXPORT_SYMBOL_NS_GPL(thc_swdma_read, "INTEL_THC");

static int write_dma_buffer(struct thc_device *dev,
       void *buffer, size_t buf_len)
{
 struct thc_dma_configuration *write_config = &dev->dma_ctx->dma_config[THC_TXDMA];
 struct thc_prd_table *prd_tbl;
 struct scatterlist *sg;
 unsigned long len_left;
 size_t ret;
 u8 nent;
 int i;

 /* There is only one PRD table for write */
 prd_tbl = &write_config->prd_tbls[0];

 if (calc_prd_entries_num(prd_tbl, buf_len, &nent) < 0) {
  dev_err(dev->dev, "Tx message length too big (%zu)\n", buf_len);
  return -EOVERFLOW;
 }

 sg = write_config->sgls[0];
 ret = sg_copy_from_buffer(sg, nent, buffer, buf_len);
 if (ret != buf_len) {
  dev_err_once(dev->dev, "Copied %zu bytes instead of requested %zu\n",
        ret, buf_len);
  return -EIO;
 }

 prd_tbl = &write_config->prd_tbls[0];
 len_left = buf_len;

 for_each_sg(write_config->sgls[0], sg, write_config->sgls_nent[0], i) {
  if (sg_dma_address(sg) == 0 || sg_dma_len(sg) == 0) {
   dev_err_once(dev->dev, "SGList: zero address or length\n");
   return -EINVAL;
  }

  prd_tbl->entries[i].dest_addr =
    sg_dma_address(sg) >> THC_ADDRESS_SHIFT;

  if (len_left < sg_dma_len(sg)) {
   prd_tbl->entries[i].len = len_left;
   prd_tbl->entries[i].end_of_prd = 1;
   break;
  }

  prd_tbl->entries[i].len = sg_dma_len(sg);
  prd_tbl->entries[i].end_of_prd = 0;

  len_left -= sg_dma_len(sg);
 }

 dma_set_prd_control(dev, i, 0, write_config);

 return 0;
}

static void thc_ensure_performance_limitations(struct thc_device *dev)
{
 unsigned long delay_usec = 0;
 /*
 * Minimum amount of delay the THC / QUICKSPI driver must wait
 * between end of write operation and begin of read operation.
 * This value shall be in 10us multiples.
 */

 if (dev->perf_limit > 0) {
  delay_usec = dev->perf_limit * 10;
  udelay(delay_usec);
 }
}

static void thc_dma_write_completion(struct thc_device *dev)
{
 thc_ensure_performance_limitations(dev);
}

/**
 * thc_dma_write - Use TXDMA to write data to touch device
 *
 * @dev: The pointer of THC private device context
 * @buffer: The pointer of write data buffer
 * @buf_len: The write data length
 *
 * Return: 0 on success, other error codes on failed.
 */

int thc_dma_write(struct thc_device *dev, void *buffer, size_t buf_len)
{
 bool restore_interrupts = false;
 u32 sts, ctrl;
 int ret;

 if (!(&dev->dma_ctx->dma_config[THC_TXDMA])->is_enabled) {
  dev_err_once(dev->dev, "The TxDMA channel is not enabled\n");
  return -EINVAL;
 }

 if (!buffer || buf_len <= 0) {
  dev_err(dev->dev, "Invalid input parameters, buffer %p\n, buf_len %zu\n",
   buffer, buf_len);
  return -EINVAL;
 }

 regmap_read(dev->thc_regmap, THC_M_PRT_WRITE_INT_STS_OFFSET, &sts);
 if (sts & THC_M_PRT_WRITE_INT_STS_THC_WRDMA_ACTIVE) {
  dev_err_once(dev->dev, "THC TxDMA is till active and can't start again\n");
  return -EBUSY;
 }

 if (mutex_lock_interruptible(&dev->thc_bus_lock))
  return -EINTR;

 regmap_read(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, &ctrl);

 ret = write_dma_buffer(dev, buffer, buf_len);
 if (ret)
  goto end;

 if (dev->perf_limit && !(ctrl & THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_HW_STS)) {
  ret = thc_interrupt_quiesce(dev, true);
  if (ret)
   goto end;

  restore_interrupts = true;
 }

 dev->write_done = false;

 dma_set_start_bit(dev, &dev->dma_ctx->dma_config[THC_TXDMA]);

 ret = wait_event_interruptible_timeout(dev->write_complete_wait, dev->write_done, 1 * HZ);
 if (ret <= 0 || !dev->write_done) {
  dev_err_once(dev->dev, "timeout for waiting TxDMA completion\n");
  ret = -ETIMEDOUT;
  goto end;
 }

 thc_dma_write_completion(dev);
 mutex_unlock(&dev->thc_bus_lock);
 return 0;

end:
 mutex_unlock(&dev->thc_bus_lock);

 if (restore_interrupts)
  ret = thc_interrupt_quiesce(dev, false);

 return ret;
}
EXPORT_SYMBOL_NS_GPL(thc_dma_write, "INTEL_THC");

Messung V0.5
C=98 H=90 G=94

¤ Dauer der Verarbeitung: 0.13 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.