summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVic Yang <victoryang@chromium.org>2013-08-29 16:42:02 +0800
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2013-09-05 03:10:09 +0000
commitb57a5fe0edfcd8fa264c2f83755f5c6ae73d8435 (patch)
tree23d2690e7261d82277ae179a56a60070c3591f0b
parentc34d0cc8bf4308046d2d213f4ff744b011e0edbe (diff)
downloadchrome-ec-b57a5fe0edfcd8fa264c2f83755f5c6ae73d8435.tar.gz
STM32L ADC driver
ADC module on STM32L is clocked by HSI oscillator, and thus we need to switch to HSI if using MSI. After the conversion, if the system is not in S0, clock is switched back to MSI again. There are several register bits that can only be written when ADC is powered down. For now, let's just power down the ADC after each conversion. Currently ADC watchdog is not working and is disabled on STM32L. BUG=chrome-os-partner:22242 TEST=Try multiple all-channel and single-channel reads in S0 and S5. BRANCH=None Change-Id: I769dda8a9c69ac9de1eb22d6d259034eef8c1ac4 Signed-off-by: Vic Yang <victoryang@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/167454
-rw-r--r--board/daisy/board.h1
-rw-r--r--board/kirby/board.h2
-rw-r--r--board/pit/board.h1
-rw-r--r--board/puppy/board.h1
-rw-r--r--chip/stm32/adc-stm32f.c (renamed from chip/stm32/adc.c)0
-rw-r--r--chip/stm32/adc-stm32l.c229
-rw-r--r--chip/stm32/build.mk2
-rw-r--r--chip/stm32/clock-stm32l.c23
-rw-r--r--chip/stm32/registers.h30
-rw-r--r--include/clock.h16
-rw-r--r--include/config.h6
11 files changed, 307 insertions, 4 deletions
diff --git a/board/daisy/board.h b/board/daisy/board.h
index b46e6172ae..1522feb4e9 100644
--- a/board/daisy/board.h
+++ b/board/daisy/board.h
@@ -39,6 +39,7 @@ enum module_id {
MODULE_I2C,
MODULE_POWER_LED,
MODULE_UART,
+ MODULE_CHIPSET,
};
/* By default, enable all console messages except keyboard */
diff --git a/board/kirby/board.h b/board/kirby/board.h
index f7fd9573ff..138f7984d1 100644
--- a/board/kirby/board.h
+++ b/board/kirby/board.h
@@ -29,6 +29,8 @@ enum module_id {
MODULE_LED_KIRBY,
MODULE_SPI,
MODULE_UART,
+ MODULE_ADC,
+ MODULE_CHIPSET,
};
/* By default, enable all console messages except keyboard */
diff --git a/board/pit/board.h b/board/pit/board.h
index 89c54951b9..2b9f6c2e29 100644
--- a/board/pit/board.h
+++ b/board/pit/board.h
@@ -32,6 +32,7 @@ enum module_id {
MODULE_POWER_LED,
MODULE_SPI,
MODULE_UART,
+ MODULE_CHIPSET,
};
/* By default, enable all console messages except keyboard */
diff --git a/board/puppy/board.h b/board/puppy/board.h
index f3b1135852..abf18142e7 100644
--- a/board/puppy/board.h
+++ b/board/puppy/board.h
@@ -32,6 +32,7 @@ enum module_id {
MODULE_POWER_LED,
MODULE_SPI,
MODULE_UART,
+ MODULE_CHIPSET,
};
/* By default, enable all console messages except keyboard */
diff --git a/chip/stm32/adc.c b/chip/stm32/adc-stm32f.c
index 22032cfdb8..22032cfdb8 100644
--- a/chip/stm32/adc.c
+++ b/chip/stm32/adc-stm32f.c
diff --git a/chip/stm32/adc-stm32l.c b/chip/stm32/adc-stm32l.c
new file mode 100644
index 0000000000..5b432ab9ae
--- /dev/null
+++ b/chip/stm32/adc-stm32l.c
@@ -0,0 +1,229 @@
+/* Copyright (c) 2012 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 "common.h"
+#include "console.h"
+#include "clock.h"
+#include "dma.h"
+#include "hooks.h"
+#include "registers.h"
+#include "stm32_adc.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+
+#define ADC_SINGLE_READ_TIMEOUT 3000 /* 3 ms */
+
+struct mutex adc_lock;
+
+static int restore_clock;
+
+static const struct dma_option dma_adc_option = {
+ STM32_DMAC_ADC, (void *)&STM32_ADC_DR,
+ STM32_DMA_CCR_MSIZE_16_BIT | STM32_DMA_CCR_PSIZE_16_BIT,
+};
+
+static inline void adc_set_channel(int sample_id, int channel)
+{
+ uint32_t mask, val;
+ volatile uint32_t *sqr_reg;
+ int reg_id;
+
+ reg_id = 5 - sample_id / 6;
+
+ mask = 0x1f << ((sample_id % 6) * 5);
+ val = channel << ((sample_id % 6) * 5);
+ sqr_reg = &STM32_ADC_SQR(reg_id);
+
+ *sqr_reg = (*sqr_reg & ~mask) | val;
+}
+
+static void adc_configure(int ain_id)
+{
+ /* Set ADC channel */
+ adc_set_channel(0, ain_id);
+
+ /* Disable DMA */
+ STM32_ADC_CR2 &= ~(1 << 8);
+
+ /* Disable scan mode */
+ STM32_ADC_CR1 &= ~(1 << 8);
+}
+
+static void adc_configure_all(void)
+{
+ int i;
+
+ /* Set ADC channels */
+ STM32_ADC_SQR1 = (ADC_CH_COUNT - 1) << 20;
+ for (i = 0; i < ADC_CH_COUNT; ++i)
+ adc_set_channel(i, adc_channels[i].channel);
+
+ /* Enable DMA */
+ STM32_ADC_CR2 |= (1 << 8);
+
+ /* Enable scan mode */
+ STM32_ADC_CR1 |= (1 << 8);
+}
+
+static inline int adc_powered(void)
+{
+ return STM32_ADC_SR & (1 << 6); /* ADONS */
+}
+
+static void adc_enable_clock(void)
+{
+ STM32_RCC_APB2ENR |= (1 << 9);
+ /* ADCCLK = HSI / 2 = 8MHz*/
+ STM32_ADC_CCR |= (1 << 16);
+}
+
+static void adc_init(void)
+{
+ /*
+ * For STM32L, ADC clock source is HSI/2 = 8 MHz. HSI must be enabled
+ * for ADC.
+ *
+ * Note that we are not powering on ADC on EC initialization because
+ * STM32L ADC module requires HSI clock. Instead, ADC module is powered
+ * on/off in adc_prepare()/adc_release().
+ */
+
+ /* Enable ADC clock. */
+ adc_enable_clock();
+
+ if (!adc_powered())
+ /* Power on ADC module */
+ STM32_ADC_CR2 |= (1 << 0); /* ADON */
+
+ /* Set right alignment */
+ STM32_ADC_CR2 &= ~(1 << 11);
+
+ /*
+ * Set sample time of all channels to 16 cycles.
+ * Conversion takes (12+16)/8M = 3.34 us.
+ */
+ STM32_ADC_SMPR1 = 0x24924892;
+ STM32_ADC_SMPR2 = 0x24924892;
+ STM32_ADC_SMPR3 = 0x24924892;
+}
+
+static void adc_prepare(void)
+{
+ if (!adc_powered()) {
+ clock_enable_module(MODULE_ADC, 1);
+ adc_init();
+ restore_clock = 1;
+ }
+}
+
+static void adc_release(void)
+{
+ if (restore_clock) {
+ clock_enable_module(MODULE_ADC, 0);
+ restore_clock = 0;
+ }
+ /*
+ * Always power down ADC.
+ * TODO(victoryang): Can we leave ADC powered?
+ */
+ if (adc_powered())
+ STM32_ADC_CR2 = 0;
+}
+
+static inline int adc_conversion_ended(void)
+{
+ return STM32_ADC_SR & (1 << 1);
+}
+
+int adc_read_channel(enum adc_channel ch)
+{
+ const struct adc_t *adc = adc_channels + ch;
+ int value;
+ timestamp_t deadline;
+
+ mutex_lock(&adc_lock);
+
+ adc_prepare();
+
+ adc_configure(adc->channel);
+
+ /* Clear EOC bit */
+ STM32_ADC_SR &= ~(1 << 1);
+
+ /* Start conversion */
+ STM32_ADC_CR2 |= (1 << 30); /* SWSTART */
+
+ /* Wait for EOC bit set */
+ deadline.val = get_time().val + ADC_SINGLE_READ_TIMEOUT;
+ value = ADC_READ_ERROR;
+ do {
+ if (adc_conversion_ended()) {
+ value = STM32_ADC_DR & ADC_READ_MAX;
+ break;
+ }
+ } while (!timestamp_expired(deadline, NULL));
+
+ adc_release();
+
+ mutex_unlock(&adc_lock);
+ return (value == ADC_READ_ERROR) ? ADC_READ_ERROR :
+ value * adc->factor_mul / adc->factor_div + adc->shift;
+}
+
+int adc_read_all_channels(int *data)
+{
+ int i;
+ int16_t raw_data[ADC_CH_COUNT];
+ const struct adc_t *adc;
+ int ret = EC_SUCCESS;
+
+ mutex_lock(&adc_lock);
+
+ adc_prepare();
+
+ adc_configure_all();
+
+ dma_start_rx(&dma_adc_option, ADC_CH_COUNT, raw_data);
+
+ /* Start conversion */
+ STM32_ADC_CR2 |= (1 << 30); /* SWSTART */
+
+ if (dma_wait(STM32_DMAC_ADC)) {
+ ret = EC_ERROR_UNKNOWN;
+ goto exit_all_channels;
+ }
+
+ for (i = 0; i < ADC_CH_COUNT; ++i) {
+ adc = adc_channels + i;
+ data[i] = raw_data[i] * adc->factor_mul / adc->factor_div +
+ adc->shift;
+ }
+
+exit_all_channels:
+ adc_release();
+ mutex_unlock(&adc_lock);
+
+ return ret;
+}
+
+static int command_adc(int argc, char **argv)
+{
+ int i;
+ int data[ADC_CH_COUNT];
+
+ if (adc_read_all_channels(data))
+ return EC_ERROR_UNKNOWN;
+ for (i = 0; i < ADC_CH_COUNT; ++i)
+ ccprintf("ADC channel \"%s\" = %d\n",
+ adc_channels[i].name, data[i]);
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(adc, command_adc,
+ NULL,
+ "Print ADC channels",
+ NULL);
diff --git a/chip/stm32/build.mk b/chip/stm32/build.mk
index 58fce53887..8db57f86bf 100644
--- a/chip/stm32/build.mk
+++ b/chip/stm32/build.mk
@@ -17,5 +17,5 @@ chip-$(CONFIG_WATCHDOG)+=watchdog.o
chip-$(HAS_TASK_KEYSCAN)+=keyboard_raw.o
chip-$(HAS_TASK_POWERLED)+=power_led.o
chip-$(CONFIG_FLASH)+=flash-$(CHIP_FAMILY).o
-chip-$(CONFIG_ADC)+=adc.o
+chip-$(CONFIG_ADC)+=adc-$(CHIP_FAMILY).o
chip-$(CONFIG_PWM)+=pwm.o
diff --git a/chip/stm32/clock-stm32l.c b/chip/stm32/clock-stm32l.c
index e89c2e3ef5..09b8f00704 100644
--- a/chip/stm32/clock-stm32l.c
+++ b/chip/stm32/clock-stm32l.c
@@ -5,6 +5,7 @@
/* Clocks and power management settings */
+#include "chipset.h"
#include "clock.h"
#include "common.h"
#include "console.h"
@@ -146,6 +147,22 @@ static void clock_set_osc(enum clock_osc osc)
}
}
+void clock_enable_module(enum module_id module, int enable)
+{
+ static uint32_t clock_mask;
+ int new_mask;
+
+ if (enable)
+ new_mask = clock_mask | (1 << module);
+ else
+ new_mask = clock_mask & ~(1 << module);
+
+ /* Only change clock if needed */
+ if ((!!new_mask) != (!!clock_mask))
+ clock_set_osc(new_mask ? OSC_HSI : OSC_MSI);
+ clock_mask = new_mask;
+}
+
void clock_init(void)
{
/*
@@ -161,15 +178,15 @@ void clock_init(void)
static void clock_chipset_startup(void)
{
/* Return to full speed */
- clock_set_osc(OSC_HSI);
+ clock_enable_module(MODULE_CHIPSET, 1);
}
DECLARE_HOOK(HOOK_CHIPSET_STARTUP, clock_chipset_startup, HOOK_PRIO_DEFAULT);
DECLARE_HOOK(HOOK_CHIPSET_RESUME, clock_chipset_startup, HOOK_PRIO_DEFAULT);
static void clock_chipset_shutdown(void)
{
- /* Drop to lower clock speed */
- clock_set_osc(OSC_MSI);
+ /* Drop to lower clock speed if no other module requires full speed */
+ clock_enable_module(MODULE_CHIPSET, 0);
}
DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, clock_chipset_shutdown, HOOK_PRIO_DEFAULT);
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, clock_chipset_shutdown, HOOK_PRIO_DEFAULT);
diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h
index 2d75a0d249..bb8dcb21e2 100644
--- a/chip/stm32/registers.h
+++ b/chip/stm32/registers.h
@@ -605,12 +605,42 @@ typedef volatile struct stm32_spi_regs stm32_spi_regs_t;
#define STM32_ADC_JOFR(n) REG32(STM32_ADC1_BASE + 0x14 + ((n)&3) * 4)
#define STM32_ADC_HTR REG32(STM32_ADC1_BASE + 0x24)
#define STM32_ADC_LTR REG32(STM32_ADC1_BASE + 0x28)
+#define STM32_ADC_SQR(n) REG32(STM32_ADC1_BASE + 0x28 + ((n)&3) * 4)
#define STM32_ADC_SQR1 REG32(STM32_ADC1_BASE + 0x2C)
#define STM32_ADC_SQR2 REG32(STM32_ADC1_BASE + 0x30)
#define STM32_ADC_SQR3 REG32(STM32_ADC1_BASE + 0x34)
#define STM32_ADC_JSQR REG32(STM32_ADC1_BASE + 0x38)
#define STM32_ADC_JDR(n) REG32(STM32_ADC1_BASE + 0x3C + ((n)&3) * 4)
#define STM32_ADC_DR REG32(STM32_ADC1_BASE + 0x4C)
+#elif defined(CHIP_FAMILY_stm32l)
+#define STM32_ADC_SR REG32(STM32_ADC1_BASE + 0x00)
+#define STM32_ADC_CR1 REG32(STM32_ADC1_BASE + 0x04)
+#define STM32_ADC_CR2 REG32(STM32_ADC1_BASE + 0x08)
+#define STM32_ADC_SMPR1 REG32(STM32_ADC1_BASE + 0x0C)
+#define STM32_ADC_SMPR2 REG32(STM32_ADC1_BASE + 0x10)
+#define STM32_ADC_SMPR3 REG32(STM32_ADC1_BASE + 0x14)
+#define STM32_ADC_JOFR1 REG32(STM32_ADC1_BASE + 0x18)
+#define STM32_ADC_JOFR2 REG32(STM32_ADC1_BASE + 0x1C)
+#define STM32_ADC_JOFR3 REG32(STM32_ADC1_BASE + 0x20)
+#define STM32_ADC_JOFR4 REG32(STM32_ADC1_BASE + 0x24)
+#define STM32_ADC_HTR REG32(STM32_ADC1_BASE + 0x28)
+#define STM32_ADC_LTR REG32(STM32_ADC1_BASE + 0x2C)
+#define STM32_ADC_SQR(n) REG32(STM32_ADC1_BASE + 0x2C + (n) * 4)
+#define STM32_ADC_SQR1 REG32(STM32_ADC1_BASE + 0x30)
+#define STM32_ADC_SQR2 REG32(STM32_ADC1_BASE + 0x34)
+#define STM32_ADC_SQR3 REG32(STM32_ADC1_BASE + 0x38)
+#define STM32_ADC_SQR4 REG32(STM32_ADC1_BASE + 0x3C)
+#define STM32_ADC_SQR5 REG32(STM32_ADC1_BASE + 0x40)
+#define STM32_ADC_JSQR REG32(STM32_ADC1_BASE + 0x44)
+#define STM32_ADC_JDR1 REG32(STM32_ADC1_BASE + 0x48)
+#define STM32_ADC_JDR2 REG32(STM32_ADC1_BASE + 0x4C)
+#define STM32_ADC_JDR3 REG32(STM32_ADC1_BASE + 0x50)
+#define STM32_ADC_JDR3 REG32(STM32_ADC1_BASE + 0x50)
+#define STM32_ADC_JDR4 REG32(STM32_ADC1_BASE + 0x54)
+#define STM32_ADC_DR REG32(STM32_ADC1_BASE + 0x58)
+#define STM32_ADC_SMPR0 REG32(STM32_ADC1_BASE + 0x5C)
+
+#define STM32_ADC_CCR REG32(STM32_ADC_BASE + 0x04)
#endif
/* --- DMA --- */
diff --git a/include/clock.h b/include/clock.h
index e70df962b1..6703249076 100644
--- a/include/clock.h
+++ b/include/clock.h
@@ -21,6 +21,22 @@ void clock_init(void);
int clock_get_freq(void);
/**
+ * Enable or disable clock for a module.
+ *
+ * Note that if the module requires a higher system clock speed than the
+ * current system clock speed, the entire system clock will be increased
+ * to allow the module to operate.
+ *
+ * When a module is disabled, the system clock will be reduced to the highest
+ * clock required by the remaining enabled modules.
+ *
+ * @param module The module for which we need to enable/disable its
+ * clock.
+ * @param enable Enable clock if non-zero; disable if zero.
+ */
+void clock_enable_module(enum module_id module, int enable);
+
+/**
* Enable or disable the PLL.
*
* @param enable Enable PLL if non-zero; disable if zero.
diff --git a/include/config.h b/include/config.h
index 3025bdccb7..7513b6aa7b 100644
--- a/include/config.h
+++ b/include/config.h
@@ -40,6 +40,12 @@
#undef CONFIG_ADC
/*
+ * ADC module has certain clock requirement. If this is defined, the ADC module
+ * should call clock_enable_module() to configure clock for ADC.
+ */
+#undef CONFIG_ADC_CLOCK
+
+/*
* Compile support for passing backlight-enable signal from x86 chipset through
* EC. This allows the EC to gate the backlight-enable signal with the lid
* switch.