ec_chip_mchp: Add ADC and DMA files

Add Microchip MEC17xx family ADC and DMA
source files for review.

BRANCH=none
BUG=
TEST=Review only.

Change-Id: Iccf19223ddd3f6774b90d5fca32079be9b0c4bcc
Signed-off-by: Scott Worley <scott.worley@microchip.corp-partner.google.com>
This commit is contained in:
Scott Worley
2017-12-21 13:56:31 -05:00
committed by chrome-bot
parent 2f1f5a5551
commit c334f648bd
4 changed files with 630 additions and 0 deletions

141
chip/mchp/adc.c Normal file
View File

@@ -0,0 +1,141 @@
/* Copyright 2017 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "adc.h"
#include "adc_chip.h"
#include "common.h"
#include "console.h"
#include "hooks.h"
#include "registers.h"
#include "task.h"
#include "timer.h"
#include "util.h"
#include "tfdp_chip.h"
/*
* Conversion on a single channel takes less than 12 ms. Set timeout to
* 15 ms so that we have a 3-ms margin.
*/
#define ADC_SINGLE_READ_TIME 15000
struct mutex adc_lock;
/*
* Volatile should not be needed.
* ADC ISR only reads task_waiting.
* Two other non-ISR routines only write task_waiting when
* interrupt is disabled or before starting ADC.
*/
static task_id_t task_waiting;
static int start_single_and_wait(int timeout)
{
int event;
task_waiting = task_get_current();
/* Start conversion */
MCHP_ADC_CTRL |= 1 << 1;
/* Wait for interrupt */
event = task_wait_event(timeout);
task_waiting = TASK_ID_INVALID;
return event != TASK_EVENT_TIMER;
}
int adc_read_channel(enum adc_channel ch)
{
const struct adc_t *adc = adc_channels + ch;
int value;
trace1(0, ADC, 0, "adc_read_channel %d", ch);
mutex_lock(&adc_lock);
trace1(0, ADC, 0,
"adc_read_channel acquired mutex. Physical channel = %d",
adc->channel);
MCHP_ADC_SINGLE = 1 << adc->channel;
if (start_single_and_wait(ADC_SINGLE_READ_TIME))
value = MCHP_ADC_READ(adc->channel) * adc->factor_mul /
adc->factor_div + adc->shift;
else
value = ADC_READ_ERROR;
trace11(0, ADC, 0,
"adc_read_channel value = 0x%08X. Releasing mutex", value);
mutex_unlock(&adc_lock);
return value;
}
int adc_read_all_channels(int *data)
{
int i;
int ret = EC_SUCCESS;
const struct adc_t *adc;
trace0(0, ADC, 0, "adc_read_all_channels");
mutex_lock(&adc_lock);
trace0(0, ADC, 0, "adc_read_all_channels acquired mutex");
MCHP_ADC_SINGLE = 0;
for (i = 0; i < ADC_CH_COUNT; ++i)
MCHP_ADC_SINGLE |= 1 << adc_channels[i].channel;
if (!start_single_and_wait(ADC_SINGLE_READ_TIME * ADC_CH_COUNT)) {
ret = EC_ERROR_TIMEOUT;
goto exit_all_channels;
}
for (i = 0; i < ADC_CH_COUNT; ++i) {
adc = adc_channels + i;
data[i] = MCHP_ADC_READ(adc->channel) * adc->factor_mul /
adc->factor_div + adc->shift;
trace12(0, ADC, 0, "adc all: data[%d] = 0x%08X", i, data[i]);
}
exit_all_channels:
mutex_unlock(&adc_lock);
trace0(0, ADC, 0, "adc_read_all_channels released mutex");
return ret;
}
/*
* Using MEC1701 direct mode interrupts. Do not
* set Interrupt Aggregator Block Enable bit
* for GIRQ containing ADC.
*/
static void adc_init(void)
{
/* clear ADC sleep enable */
MCHP_PCR_SLP_DIS_DEV(MCHP_PCR_ADC);
/* Activate ADC module */
MCHP_ADC_CTRL |= 1 << 0;
/* Enable interrupt */
task_waiting = TASK_ID_INVALID;
MCHP_INT_ENABLE(MCHP_ADC_GIRQ) = MCHP_ADC_GIRQ_SINGLE_BIT;
task_enable_irq(MCHP_IRQ_ADC_SNGL);
}
DECLARE_HOOK(HOOK_INIT, adc_init, HOOK_PRIO_INIT_ADC);
void adc_interrupt(void)
{
/* Clear interrupt status bit */
MCHP_ADC_CTRL |= 1 << 7;
MCHP_INT_SOURCE(MCHP_ADC_GIRQ) = MCHP_ADC_GIRQ_SINGLE_BIT;
if (task_waiting != TASK_ID_INVALID)
task_wake(task_waiting);
}
DECLARE_IRQ(MCHP_IRQ_ADC_SNGL, adc_interrupt, 2);

33
chip/mchp/adc_chip.h Normal file
View File

@@ -0,0 +1,33 @@
/* Copyright 2017 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/* MCHP MEC specific ADC module for Chrome EC */
#ifndef __CROS_EC_ADC_CHIP_H
#define __CROS_EC_ADC_CHIP_H
/* Data structure to define ADC channels. */
struct adc_t {
const char *name;
int factor_mul;
int factor_div;
int shift;
int channel;
};
/*
* Boards must provide this list of ADC channel definitions.
* This must match the enum adc_channel list provided by the board.
*/
extern const struct adc_t adc_channels[];
/* Minimum and maximum values returned by adc_read_channel(). */
#define ADC_READ_MIN 0
#define ADC_READ_MAX 1023
/* Just plain id mapping for code readability */
#define MCHP_ADC_CH(x) (x)
#endif /* __CROS_EC_ADC_CHIP_H */

388
chip/mchp/dma.c Normal file
View File

@@ -0,0 +1,388 @@
/* Copyright 2017 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "common.h"
#include "console.h"
#include "dma.h"
#include "hooks.h"
#include "registers.h"
#include "task.h"
#include "timer.h"
#include "util.h"
#include "tfdp_chip.h"
/* Console output macros */
#define CPUTS(outstr) cputs(CC_DMA, outstr)
#define CPRINTS(format, args...) cprints(CC_DMA, format, ## args)
dma_chan_t *dma_get_channel(enum dma_channel channel)
{
dma_chan_t *pd = NULL;
if (channel < MCHP_DMAC_COUNT) {
pd = (dma_chan_t *)(MCHP_DMA_BASE + MCHP_DMA_CH_OFS +
(channel << MCHP_DMA_CH_OFS_BITPOS));
}
return pd;
}
void dma_disable(enum dma_channel channel)
{
if (channel < MCHP_DMAC_COUNT) {
if (MCHP_DMA_CH_CTRL(channel) & MCHP_DMA_RUN)
MCHP_DMA_CH_CTRL(channel) &= ~(MCHP_DMA_RUN);
if (MCHP_DMA_CH_ACT(channel) & MCHP_DMA_ACT_EN)
MCHP_DMA_CH_ACT(channel) = 0;
}
}
void dma_disable_all(void)
{
uint16_t ch;
uint32_t dummy = 0;
for (ch = 0; ch < MCHP_DMAC_COUNT; ch++) {
/* Abort any current transfer. */
MCHP_DMA_CH_CTRL(ch) |= MCHP_DMA_ABORT;
/* Disable the channel. */
MCHP_DMA_CH_CTRL(ch) &= ~(MCHP_DMA_RUN);
MCHP_DMA_CH_ACT(ch) = 0;
}
/* Soft-reset the block. */
MCHP_DMA_MAIN_CTRL = MCHP_DMA_MAIN_CTRL_SRST;
dummy += MCHP_DMA_MAIN_CTRL;
MCHP_DMA_MAIN_CTRL = MCHP_DMA_MAIN_CTRL_ACT;
}
/**
* Prepare a channel for use and start it
*
* @param chan Channel to read
* @param count Number of bytes to transfer
* @param periph Pointer to peripheral data register
* @param memory Pointer to memory address for receive/transmit
* @param flags DMA flags for the control register, normally:
* MCHP_DMA_INC_MEM | MCHP_DMA_TO_DEV for tx
* MCHP_DMA_INC_MEM for rx
* Plus transfer unit length(1, 2, or 4) in bits[22:20]
* @note MCHP DMA does not require address aliasing. Because count
* is the number of bytes to transfer memory start - memory end = count.
*/
static void prepare_channel(enum dma_channel ch, unsigned int count,
void *periph, void *memory, unsigned int flags)
{
if (ch < MCHP_DMAC_COUNT) {
MCHP_DMA_CH_CTRL(ch) = 0;
MCHP_DMA_CH_MEM_START(ch) = (uint32_t)memory;
MCHP_DMA_CH_MEM_END(ch) = (uint32_t)memory + count;
MCHP_DMA_CH_DEV_ADDR(ch) = (uint32_t)periph;
MCHP_DMA_CH_CTRL(ch) = flags;
MCHP_DMA_CH_ACT(ch) = MCHP_DMA_ACT_EN;
}
}
void dma_go(dma_chan_t *chan)
{
/* Flush data in write buffer so that DMA can get the
* latest data.
*/
asm volatile("dsb;");
if (chan != NULL)
chan->ctrl |= MCHP_DMA_RUN;
}
void dma_go_chan(enum dma_channel ch)
{
asm volatile("dsb;");
if (ch < MCHP_DMAC_COUNT)
MCHP_DMA_CH_CTRL(ch) |= MCHP_DMA_RUN;
}
void dma_prepare_tx(const struct dma_option *option, unsigned count,
const void *memory)
{
if (option != NULL)
/*
* Cast away const for memory pointer; this is ok because
* we know we're preparing the channel for transmit.
*/
prepare_channel(option->channel, count, option->periph,
(void *)memory,
MCHP_DMA_INC_MEM |
MCHP_DMA_TO_DEV |
MCHP_DMA_DEV(option->channel) |
option->flags);
}
void dma_xfr_prepare_tx(const struct dma_option *option, uint32_t count,
const void *memory, uint32_t dma_xfr_units)
{
uint32_t nflags;
if (option != NULL) {
nflags = option->flags & ~(MCHP_DMA_XFER_SIZE_MASK);
nflags |= MCHP_DMA_XFER_SIZE(dma_xfr_units & 0x07);
/*
* Cast away const for memory pointer; this is ok because
* we know we're preparing the channel for transmit.
*/
prepare_channel(option->channel, count, option->periph,
(void *)memory,
MCHP_DMA_INC_MEM |
MCHP_DMA_TO_DEV |
MCHP_DMA_DEV(option->channel) |
nflags);
}
}
void dma_start_rx(const struct dma_option *option, unsigned count,
void *memory)
{
if (option != NULL) {
prepare_channel(option->channel, count, option->periph,
memory,
MCHP_DMA_INC_MEM |
MCHP_DMA_DEV(option->channel) |
option->flags);
dma_go_chan(option->channel);
}
}
/*
* Configure and start DMA channel for read from device and write to
* memory. Allow caller to override DMA transfer unit length.
*/
void dma_xfr_start_rx(const struct dma_option *option,
uint32_t dma_xfr_ulen,
uint32_t count, void *memory)
{
uint32_t ch, ctrl;
if (option != NULL) {
ch = option->channel;
if (ch < MCHP_DMAC_COUNT) {
MCHP_DMA_CH_CTRL(ch) = 0;
MCHP_DMA_CH_MEM_START(ch) = (uint32_t)memory;
MCHP_DMA_CH_MEM_END(ch) = (uint32_t)memory +
count;
MCHP_DMA_CH_DEV_ADDR(ch) =
(uint32_t)option->periph;
ctrl = option->flags &
~(MCHP_DMA_XFER_SIZE_MASK);
ctrl |= MCHP_DMA_INC_MEM;
ctrl |= MCHP_DMA_XFER_SIZE(dma_xfr_ulen);
ctrl |= MCHP_DMA_DEV(option->channel);
MCHP_DMA_CH_CTRL(ch) = ctrl;
MCHP_DMA_CH_ACT(ch) = MCHP_DMA_ACT_EN;
}
dma_go_chan(option->channel);
}
}
/*
* Return the number of bytes transferred.
* The number of bytes transferred can be easily determinted
* from the difference in DMA memory start address register
* and memory end address register. No need to look at DMA
* transfer size field because the hardware increments memory
* start address by unit size on each unit tranferred.
* Why is a signed integer being used for a count value?
*/
int dma_bytes_done(dma_chan_t *chan, int orig_count)
{
int bcnt = 0;
if (chan != NULL) {
if (chan->ctrl & MCHP_DMA_RUN)
bcnt = (int)chan->mem_end;
bcnt -= (int)chan->mem_start;
bcnt = orig_count - bcnt;
}
return bcnt;
}
int dma_bytes_done_chan(enum dma_channel ch, uint32_t orig_count)
{
uint32_t cnt;
cnt = 0;
if (ch < MCHP_DMAC_COUNT)
if (MCHP_DMA_CH_CTRL(ch) & MCHP_DMA_RUN)
cnt = (uint32_t)orig_count -
(MCHP_DMA_CH_MEM_END(ch) -
MCHP_DMA_CH_MEM_START(ch));
return (int)cnt;
}
/*
* Initialize DMA block.
* Clear PCR DMA sleep enable.
* Soft-Reset block should clear after one clock but read-back to
* be safe.
* Set block activate bit after reset.
*/
void dma_init(void)
{
MCHP_PCR_SLP_DIS_DEV(MCHP_PCR_DMA);
MCHP_DMA_MAIN_CTRL = MCHP_DMA_MAIN_CTRL_SRST;
MCHP_DMA_MAIN_CTRL;
MCHP_DMA_MAIN_CTRL = MCHP_DMA_MAIN_CTRL_ACT;
}
int dma_wait(enum dma_channel channel)
{
timestamp_t deadline;
if (channel < MCHP_DMAC_COUNT) {
if (MCHP_DMA_CH_ACT(channel) == 0)
return EC_SUCCESS;
deadline.val = get_time().val + DMA_TRANSFER_TIMEOUT_US;
while (!(MCHP_DMA_CH_ISTS(channel) &
MCHP_DMA_STS_DONE)) {
if (deadline.val <= get_time().val)
return EC_ERROR_TIMEOUT;
udelay(DMA_POLLING_INTERVAL_US);
}
return EC_SUCCESS;
}
return EC_ERROR_INVAL;
}
/*
* Clear all interrupt status in specified DMA channel
*/
void dma_clear_isr(enum dma_channel channel)
{
if (channel < MCHP_DMAC_COUNT)
MCHP_DMA_CH_ISTS(channel) = 0x0f;
}
void dma_cfg_buffers(enum dma_channel ch, const void *membuf,
uint32_t nb, const void *pdev)
{
if (ch < MCHP_DMAC_COUNT) {
MCHP_DMA_CH_MEM_START(ch) = (uint32_t)membuf;
MCHP_DMA_CH_MEM_END(ch) = (uint32_t)membuf + nb;
MCHP_DMA_CH_DEV_ADDR(ch) = (uint32_t)pdev;
}
}
/*
* ch = zero based DMA channel number
* unit_len = DMA unit size 1, 2 or 4 bytes
* flags
* b[0] = direction, 0=device_to_memory, 1=memory_to_device
* b[1] = 1 increment memory address
* b[2] = 1 increment device address
* b[3] = disable HW flow control
*/
void dma_cfg_xfr(enum dma_channel ch, uint8_t unit_len,
uint8_t dev_id, uint8_t flags)
{
uint32_t ctrl;
if (ch < MCHP_DMAC_COUNT) {
ctrl = MCHP_DMA_XFER_SIZE(unit_len & 0x07);
ctrl += MCHP_DMA_DEV(dev_id & MCHP_DMA_DEV_MASK0);
if (flags & 0x01)
ctrl |= MCHP_DMA_TO_DEV;
if (flags & 0x02)
ctrl |= MCHP_DMA_INC_MEM;
if (flags & 0x04)
ctrl |= MCHP_DMA_INC_DEV;
if (flags & 0x08)
ctrl |= MCHP_DMA_DIS_HW_FLOW;
MCHP_DMA_CH_CTRL(ch) = ctrl;
}
}
void dma_clr_chan(enum dma_channel ch)
{
if (ch < MCHP_DMAC_COUNT) {
MCHP_DMA_CH_ACT(ch) = 0;
MCHP_DMA_CH_CTRL(ch) = 0;
MCHP_DMA_CH_IEN(ch) = 0;
MCHP_DMA_CH_ISTS(ch) = 0xff;
MCHP_DMA_CH_FSM_RO(ch) = MCHP_DMA_CH_ISTS(ch);
MCHP_DMA_CH_ACT(ch) = 1;
}
}
void dma_run(enum dma_channel ch)
{
if (ch < MCHP_DMAC_COUNT) {
if (MCHP_DMA_CH_CTRL(ch) & MCHP_DMA_DIS_HW_FLOW)
MCHP_DMA_CH_CTRL(ch) |= MCHP_DMA_SW_GO;
else
MCHP_DMA_CH_CTRL(ch) |= MCHP_DMA_RUN;
}
}
/*
* Check if DMA channel is done or stopped on error
* Returns 0 not done or stopped on error
* Returns non-zero if done or stopped.
* Caller should check bit pattern for specific bit,
* done, flow control error, and bus error.
*/
uint32_t dma_is_done_chan(enum dma_channel ch)
{
if (ch < MCHP_DMAC_COUNT)
return (uint32_t)(MCHP_DMA_CH_ISTS(ch) & 0x07);
return 0;
}
/*
* Use DMA Channel 0 CRC32 ALU to compute CRC32 of data.
* Hardware implements IEEE 802.3 CRC32.
* IEEE 802.3 CRC32 initial value = 0xffffffff.
* Data must be aligned >= 4-bytes and number of bytes must
* be a multiple of 4.
*/
int dma_crc32_start(const uint8_t *mstart, const uint32_t nbytes, int ien)
{
if ((mstart == NULL) || (nbytes == 0))
return EC_ERROR_INVAL;
if ((((uint32_t)mstart | nbytes) & 0x03) != 0)
return EC_ERROR_INVAL;
MCHP_DMA_CH_ACT(0) = 0;
MCHP_DMA_CH_CTRL(0) = 0;
MCHP_DMA_CH_IEN(0) = 0;
MCHP_DMA_CH_ISTS(0) = 0xff;
MCHP_DMA_CH0_CRC32_EN = 1;
MCHP_DMA_CH0_CRC32_DATA = 0xfffffffful;
/* program device address to point to read-only register */
MCHP_DMA_CH_DEV_ADDR(0) = (uint32_t)(MCHP_DMA_CH_BASE + 0x1c);
MCHP_DMA_CH_MEM_START(0) = (uint32_t)mstart;
MCHP_DMA_CH_MEM_END(0) = (uint32_t)mstart + nbytes;
if (ien != 0)
MCHP_DMA_CH_IEN(0) = 0x07;
MCHP_DMA_CH_ACT(0) = 1;
MCHP_DMA_CH_CTRL(0) = MCHP_DMA_TO_DEV + MCHP_DMA_INC_MEM +
MCHP_DMA_DIS_HW_FLOW + MCHP_DMA_XFER_SIZE(4);
MCHP_DMA_CH_CTRL(0) |= MCHP_DMA_SW_GO;
return EC_SUCCESS;
}

68
chip/mchp/dma_chip.h Normal file
View File

@@ -0,0 +1,68 @@
/* Copyright 2017 The Chromium OS Authors. All rights reserved
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* MCHP MEC DMA controller chip level API
*/
/** @file dma_chip.h
*MCHP MEC Direct Memory Access block
*/
/** @defgroup MEC dma
*/
#ifndef _DMA_CHIP_H
#define _DMA_CHIP_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Place any C interfaces here */
void dma_xfr_start_rx(const struct dma_option *option,
uint32_t dma_xfr_ulen,
uint32_t count, void *memory);
void dma_xfr_prepare_tx(const struct dma_option *option, uint32_t count,
const void *memory, uint32_t dma_xfr_units);
void dma_clr_chan(enum dma_channel ch);
void dma_cfg_buffers(enum dma_channel ch, const void *membuf,
uint32_t nb, const void *pdev);
/*
* ch = zero based DMA channel number
* unit_len = DMA unit size 1, 2 or 4 bytes
* flags
* b[0] = direction, 0=device_to_memory, 1=memory_to_device
* b[1] = 1 increment memory address
* b[2] = 1 increment device address
* b[3] = disable HW flow control
*/
#define DMA_FLAG_D2M 0
#define DMA_FLAG_M2D 1
#define DMA_FLAG_INCR_MEM 2
#define DMA_FLAG_INCR_DEV 4
#define DMA_FLAG_SW_FLOW 8
void dma_cfg_xfr(enum dma_channel ch, uint8_t unit_len,
uint8_t dev_id, uint8_t flags);
void dma_run(enum dma_channel ch);
uint32_t dma_is_done_chan(enum dma_channel ch);
int dma_crc32_start(const uint8_t *mstart, const uint32_t nbytes, int ien);
#ifdef __cplusplus
}
#endif
#endif /* #ifndef _DMA_CHIP_H */
/** @}
*/