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 similarity index 100% rename from chip/stm32/adc.c rename to 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 @@ -20,6 +20,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. * diff --git a/include/config.h b/include/config.h index 3025bdccb7..7513b6aa7b 100644 --- a/include/config.h +++ b/include/config.h @@ -39,6 +39,12 @@ /* Compile chip support for analog-to-digital convertor */ #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