This reduces power consumption on grunt by over 3W when sitting at the depthcharge recovery screen. BUG=b:109749762 TEST=Booted grunt in the recovery screen and made sure it continued to work. Change-Id: Id079c099ee4cf6a07724241af4400063f4551668 Signed-off-by: Raul E Rangel <rrangel@chromium.org> Reviewed-on: https://review.coreboot.org/28245 Reviewed-by: Martin Roth <martinroth@google.com> Reviewed-by: Richard Spiegel <richard.spiegel@silverbackltd.com> Reviewed-by: Julius Werner <jwerner@google.com> Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
		
			
				
	
	
		
			269 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			269 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * This file is part of the libpayload project.
 | 
						|
 *
 | 
						|
 * Copyright 2018 Google LLC.
 | 
						|
 *
 | 
						|
 * Redistribution and use in source and binary forms, with or without
 | 
						|
 * modification, are permitted provided that the following conditions
 | 
						|
 * are met:
 | 
						|
 * 1. Redistributions of source code must retain the above copyright
 | 
						|
 *    notice, this list of conditions and the following disclaimer.
 | 
						|
 * 2. Redistributions in binary form must reproduce the above copyright
 | 
						|
 *    notice, this list of conditions and the following disclaimer in the
 | 
						|
 *    documentation and/or other materials provided with the distribution.
 | 
						|
 * 3. The name of the author may not be used to endorse or promote products
 | 
						|
 *    derived from this software without specific prior written permission.
 | 
						|
 *
 | 
						|
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 | 
						|
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
						|
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | 
						|
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 | 
						|
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | 
						|
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 | 
						|
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 | 
						|
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
						|
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | 
						|
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 | 
						|
 * SUCH DAMAGE.
 | 
						|
 */
 | 
						|
 | 
						|
#include <libpayload.h>
 | 
						|
#include <arch/apic.h>
 | 
						|
#include <arch/cpuid.h>
 | 
						|
#include <arch/msr.h>
 | 
						|
#include <exception.h>
 | 
						|
 | 
						|
#define APIC_BASE_MSR			0x0000001B
 | 
						|
#define APIC_BASE_MASK			(0xFFFFFFFULL << 12)
 | 
						|
 | 
						|
#define CPUID_XAPIC_ENABLED_BIT		(1 << 9)
 | 
						|
#define CPUID_XAPIC2_ENABLED_BIT	(1 << 21)
 | 
						|
 | 
						|
#define XAPIC_ENABLED_BIT		(1 << 11)
 | 
						|
#define X2APIC_ENABLED_BIT		(1 << 10)
 | 
						|
#define APIC_MASKED_BIT			(1 << 16)
 | 
						|
#define APIC_SW_ENABLED_BIT		(1 << 8)
 | 
						|
 | 
						|
#define APIC_ID				0x020
 | 
						|
#define   APIC_ID_SHIFT			24
 | 
						|
#define   APIC_ID_MASK			(0xFFUL << APIC_ID_SHIFT)
 | 
						|
#define APIC_VERSION			0x030
 | 
						|
#define   APIC_MAX_LVT_SHIFT		16
 | 
						|
#define   APIC_MAX_LVT_MASK		(0xFFUL << APIC_MAX_LVT_SHIFT)
 | 
						|
#define APIC_TASK_PRIORITY		0x080
 | 
						|
#define   APIC_TASK_PRIORITY_MASK	0xFFUL
 | 
						|
#define APIC_EOI			0x0B0
 | 
						|
#define APIC_SPURIOUS			0x0F0
 | 
						|
#define APIC_LVT_TIMER			0x320
 | 
						|
#define APIC_TIMER_INIT_COUNT		0x380
 | 
						|
#define APIC_TIMER_CUR_COUNT		0x390
 | 
						|
#define APIC_TIMER_DIV_CFG		0x3E0
 | 
						|
 | 
						|
#define APIC_LVT_SIZE			0x010
 | 
						|
 | 
						|
#define APIC_TIMER_VECTOR		0x20
 | 
						|
 | 
						|
static uint32_t apic_bar;
 | 
						|
static int _apic_initialized;
 | 
						|
// TODO: Build a lookup table to avoid calculating it.
 | 
						|
static uint32_t ticks_per_ms;
 | 
						|
static volatile uint8_t timer_waiting;
 | 
						|
 | 
						|
enum APIC_CAPABILITY {
 | 
						|
	DISABLED = 0,
 | 
						|
	XACPI = 1 << 0,
 | 
						|
	X2ACPI = 1 << 1
 | 
						|
};
 | 
						|
 | 
						|
int apic_initialized(void)
 | 
						|
{
 | 
						|
	return _apic_initialized;
 | 
						|
}
 | 
						|
 | 
						|
static inline uint32_t apic_read32(uint32_t offset)
 | 
						|
{
 | 
						|
	return read32((void *)(apic_bar + offset));
 | 
						|
}
 | 
						|
 | 
						|
static inline void apic_write32(uint32_t offset, uint32_t value)
 | 
						|
{
 | 
						|
	write32((void *)(apic_bar + offset), value);
 | 
						|
}
 | 
						|
 | 
						|
uint8_t apic_id(void)
 | 
						|
{
 | 
						|
	die_if(!apic_bar, "APIC is not initialized");
 | 
						|
 | 
						|
	uint8_t id =
 | 
						|
		(apic_read32(APIC_ID) & APIC_ID_MASK) >> APIC_ID_SHIFT;
 | 
						|
 | 
						|
	return id;
 | 
						|
}
 | 
						|
 | 
						|
void apic_delay(unsigned int usec)
 | 
						|
{
 | 
						|
	die_if(!ticks_per_ms, "apic_init_timer was not run.");
 | 
						|
	die_if(timer_waiting, "timer already started.");
 | 
						|
	die_if(!interrupts_enabled(), "Interrupts disabled.");
 | 
						|
 | 
						|
	/* The order is important so we don't underflow */
 | 
						|
	uint64_t ticks = usec * ticks_per_ms / USECS_PER_MSEC;
 | 
						|
 | 
						|
	/* Not enough resolution */
 | 
						|
	if (!ticks)
 | 
						|
		return;
 | 
						|
 | 
						|
	/* Disable interrupts so we don't get a race condition between
 | 
						|
	 * starting the timer and the hlt instruction. */
 | 
						|
	disable_interrupts();
 | 
						|
 | 
						|
	timer_waiting = 1;
 | 
						|
 | 
						|
	apic_write32(APIC_TIMER_INIT_COUNT, ticks);
 | 
						|
 | 
						|
	/* Loop in case another interrupt has fired and resumed execution. */
 | 
						|
	do {
 | 
						|
		asm volatile(
 | 
						|
			"sti\n\t"
 | 
						|
			"hlt\n\t"
 | 
						|
			/* Disable interrupts to prevent a race condition
 | 
						|
			 * between checking timer_waiting and executing the hlt
 | 
						|
			 * instruction again. */
 | 
						|
			"cli\n\t");
 | 
						|
	} while (timer_waiting);
 | 
						|
 | 
						|
	/* Leave hardware interrupts enabled. */
 | 
						|
	enable_interrupts();
 | 
						|
}
 | 
						|
 | 
						|
static void timer_interrupt_handler(u8 vector)
 | 
						|
{
 | 
						|
	timer_waiting = 0;
 | 
						|
}
 | 
						|
 | 
						|
void apic_eoi(void)
 | 
						|
{
 | 
						|
	die_if(!apic_bar, "APIC is not initialized");
 | 
						|
 | 
						|
	apic_write32(APIC_EOI, 0);
 | 
						|
}
 | 
						|
 | 
						|
static enum APIC_CAPABILITY apic_capabilities(void)
 | 
						|
{
 | 
						|
	uint32_t eax, ebx, ecx, edx;
 | 
						|
 | 
						|
	cpuid(1, eax, ebx, ecx, edx);
 | 
						|
 | 
						|
	enum APIC_CAPABILITY capabilities = DISABLED;
 | 
						|
 | 
						|
	if (edx & CPUID_XAPIC_ENABLED_BIT)
 | 
						|
		capabilities |= XACPI;
 | 
						|
 | 
						|
	if (ecx & CPUID_XAPIC2_ENABLED_BIT)
 | 
						|
		capabilities |= X2ACPI;
 | 
						|
 | 
						|
	return capabilities;
 | 
						|
}
 | 
						|
 | 
						|
static uint8_t apic_max_lvt_entries(void)
 | 
						|
{
 | 
						|
	die_if(!apic_bar, "APIC is not initialized");
 | 
						|
 | 
						|
	uint32_t reg = apic_read32(APIC_VERSION);
 | 
						|
	reg &= APIC_MAX_LVT_MASK;
 | 
						|
	reg >>= APIC_MAX_LVT_SHIFT;
 | 
						|
 | 
						|
	return (uint8_t)reg;
 | 
						|
}
 | 
						|
 | 
						|
static void apic_reset_all_lvts(void)
 | 
						|
{
 | 
						|
	uint8_t max = apic_max_lvt_entries();
 | 
						|
	for (int i = 0; i <= max; ++i) {
 | 
						|
		uint32_t offset = APIC_LVT_TIMER + APIC_LVT_SIZE * i;
 | 
						|
		apic_write32(offset, APIC_MASKED_BIT);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void apic_set_task_priority(uint8_t priority)
 | 
						|
{
 | 
						|
	die_if(!apic_bar, "APIC is not initialized");
 | 
						|
 | 
						|
	uint32_t tpr = apic_read32(APIC_TASK_PRIORITY);
 | 
						|
	tpr &= ~APIC_TASK_PRIORITY_MASK;
 | 
						|
	tpr |= priority;
 | 
						|
 | 
						|
	apic_write32(APIC_TASK_PRIORITY, priority);
 | 
						|
}
 | 
						|
 | 
						|
static void apic_init_timer(void)
 | 
						|
{
 | 
						|
	die_if(!apic_bar, "APIC is not initialized");
 | 
						|
 | 
						|
	apic_write32(APIC_LVT_TIMER, APIC_MASKED_BIT);
 | 
						|
 | 
						|
	/* Divide the clock by 1. */
 | 
						|
	apic_write32(APIC_TIMER_DIV_CFG, 0xB);
 | 
						|
 | 
						|
	/* Calibrate the APIC timer */
 | 
						|
	if (!ticks_per_ms) {
 | 
						|
		/* Set APIC init counter to MAX and count for 1 ms */
 | 
						|
		apic_write32(APIC_TIMER_INIT_COUNT, UINT32_MAX);
 | 
						|
 | 
						|
		/* This is safe because apic_initialized() returns false so
 | 
						|
		 * arch_ndelay() falls back to a busy loop. */
 | 
						|
		mdelay(1);
 | 
						|
 | 
						|
		ticks_per_ms =
 | 
						|
			UINT32_MAX - apic_read32(APIC_TIMER_CUR_COUNT);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Clear the count so we don't get any stale interrupts */
 | 
						|
	apic_write32(APIC_TIMER_INIT_COUNT, 0);
 | 
						|
 | 
						|
	/* Unmask the timer and set the vector. */
 | 
						|
	apic_write32(APIC_LVT_TIMER, APIC_TIMER_VECTOR);
 | 
						|
}
 | 
						|
 | 
						|
static void apic_sw_enable(void)
 | 
						|
{
 | 
						|
	uint32_t reg = apic_read32(APIC_SPURIOUS);
 | 
						|
	if (reg & APIC_SW_ENABLED_BIT)
 | 
						|
		return;
 | 
						|
 | 
						|
	reg |= APIC_SW_ENABLED_BIT;
 | 
						|
 | 
						|
	apic_write32(APIC_SPURIOUS, reg);
 | 
						|
}
 | 
						|
 | 
						|
void apic_init(void)
 | 
						|
{
 | 
						|
	uint64_t apic_bar_reg;
 | 
						|
 | 
						|
	printf("APIC Init Started\n");
 | 
						|
 | 
						|
	die_if(apic_initialized(), "APIC already initialized");
 | 
						|
	die_if(!(apic_capabilities() & XACPI), "APIC is not supported");
 | 
						|
 | 
						|
	apic_bar_reg = _rdmsr(APIC_BASE_MSR);
 | 
						|
 | 
						|
	die_if(!(apic_bar_reg & XAPIC_ENABLED_BIT), "APIC is not enabled");
 | 
						|
	die_if(apic_bar_reg & X2APIC_ENABLED_BIT,
 | 
						|
	       "APIC is configured in x2APIC mode which is not supported");
 | 
						|
 | 
						|
	apic_bar = (uint32_t)(apic_bar_reg & APIC_BASE_MASK);
 | 
						|
 | 
						|
	apic_reset_all_lvts();
 | 
						|
	apic_set_task_priority(0);
 | 
						|
 | 
						|
	apic_sw_enable();
 | 
						|
 | 
						|
	apic_init_timer();
 | 
						|
 | 
						|
	set_interrupt_handler(APIC_TIMER_VECTOR, &timer_interrupt_handler);
 | 
						|
 | 
						|
	_apic_initialized = 1;
 | 
						|
 | 
						|
	printf("APIC Configured\n");
 | 
						|
}
 |