blob: bf68621e7657b3a3d0d307d991412dbd730fca30 [file] [log] [blame]
/*
* Copyright 2011 Google Inc. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*/
/* Implementation of per-board codec beeping */
#include <stdio.h>
#include "base/algorithm.h"
#include "base/container_of.h"
#include "base/io.h"
#include "base/time.h"
#include "base/xalloc.h"
#include "drivers/bus/pci/pci.h"
#include "drivers/sound/hda_codec.h"
#define HDA_ICII_COMMAND_REG 0x60
#define HDA_ICII_RESPONSE_REG 0x64
#define HDA_ICII_ICS_REG 0x68
#define HDA_ICII_ICS_BUSY (1 << 0)
#define HDA_ICII_ICS_VALID (1 << 1)
/* Common node IDs. */
#define HDA_ROOT_NODE 0x00
/* HDA verbs. */
#define HDA_VERB(nid, verb, param) ((nid) << 20 | (verb) << 8 | (param))
#define HDA_VERB_GET_PARAMS 0xF00
#define HDA_VERB_SET_BEEP 0x70A
/* GET_PARAMS parameter IDs. */
#define GET_PARAMS_NODE_COUNT 0x04
#define GET_PARAMS_AUDIO_GROUP_CAPS 0x08
#define GET_PARAMS_AUDIO_WIDGET_CAPS 0x09
/* Get Sub-node count fields. */
#define AUDIO_NODE_NUM_SUB_NODES(x) (x & 0x000000ff)
#define AUDIO_NODE_FIRST_SUB_NODE(x) ((x >> 16) & 0x000000ff)
/* Get Audio Function Group Capabilities fields. */
#define AUDIO_GROUP_CAPS_BEEP_GEN 0x10000
/* Get Audio Widget Capabilities fields. */
#define AUDIO_WIDGET_TYPE_BEEP 0x7
#define AUDIO_WIDGET_TYPE(x) (((x) >> 20) & 0x0f)
#define BEEP_FREQ_BASE 12000
/*
* Wait 50usec for the codec to indicate it is ready. No response would
* imply that the codec is non-operative.
*/
static int wait_for_ready(uint32_t base)
{
// Use a 50 usec timeout - the Linux kernel uses the same duration.
int timeout = 50;
while (timeout--) {
uint32_t reg32 = read32((uint32_t *)(base + HDA_ICII_ICS_REG));
asm("" ::: "memory");
if (!(reg32 & HDA_ICII_ICS_BUSY))
return 0;
udelay(1);
}
printf("Audio: wait_for_ready timeout\n");
return -1;
}
/*
* Wait 50usec for the codec to indicate that it accepted the previous
* command. No response would imply that the code is non-operative.
*/
static int wait_for_response(uint32_t base, uint32_t *response)
{
uint32_t reg32;
// Send the verb to the codec.
reg32 = read32((uint32_t *)(base + HDA_ICII_ICS_REG));
reg32 |= HDA_ICII_ICS_BUSY | HDA_ICII_ICS_VALID;
write32((uint32_t *)(base + HDA_ICII_ICS_REG), reg32);
// Use a 50 usec timeout - the Linux kernel uses the same duration.
int timeout = 50;
while (timeout--) {
reg32 = read32((uint32_t *)(base + HDA_ICII_ICS_REG));
asm("" ::: "memory");
if ((reg32 & (HDA_ICII_ICS_VALID | HDA_ICII_ICS_BUSY)) ==
HDA_ICII_ICS_VALID) {
if (response != NULL)
*response = read32(
(uint32_t *)(base +
HDA_ICII_RESPONSE_REG));
return 0;
}
udelay(1);
}
printf("Audio: wait_for_response timeout\n");
return -1;
}
/*
* Wait for the codec to be ready, write the verb, then wait for the
* codec to send a valid response.
*/
static int exec_one_verb(uint32_t base, uint32_t val, uint32_t *response)
{
if (wait_for_ready(base) == -1)
return -1;
write32((uint32_t *)(base + HDA_ICII_COMMAND_REG), val);
if (wait_for_response(base, response) == -1)
return -1;
return 0;
}
/* Write one verb and ignore the response. */
static int write_one_verb(uint32_t base, uint32_t val)
{
return exec_one_verb(base, val, NULL);
}
/* Supported sound devices. */
typedef struct pci_device_id {
uint16_t vendor;
uint16_t device;
} pci_device_id;
#define PCI_VENDOR_ID_INTEL 0x8086
#define PCI_DEVICE_ID_INTEL_COUGARPOINT_HDA 0x1c20
#define PCI_DEVICE_ID_INTEL_PANTHERPOINT_HDA 0x1e20
#define PCI_DEVICE_ID_INTEL_LYNXPOINT_H_HDA 0x8c20
#define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_HDA 0x9c20
#define PCI_DEVICE_ID_INTEL_WILDCATPOINT_LP_HDA 0x9ca0
/* Supported sound devices. */
static struct pci_device_id supported[] = {
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_HDA},
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_HDA},
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_LYNXPOINT_H_HDA},
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_HDA},
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_WILDCATPOINT_LP_HDA},
{}
};
/* Find the base address to talk to the HDA codec. */
static uint32_t get_hda_base(void)
{
pcidev_t hda_dev;
for (int i = 0; i < ARRAY_SIZE(supported); i++) {
if (pci_find_device(supported[i].vendor, supported[i].device,
&hda_dev)) {
return pci_read_resource(hda_dev, 0) & ~0xf;
}
}
printf("Audio: Controller not found!\n");
return 0;
}
/*
* Gets the count of sub-nodes and node id they start at for the given
* super-node.
*/
static uint32_t get_subnode_info(uint32_t base,
uint32_t nid,
uint32_t *num_sub_nodes,
uint32_t *start_sub_node_nid)
{
int rc;
uint32_t response;
rc = exec_one_verb(base,
HDA_VERB(nid,
HDA_VERB_GET_PARAMS,
GET_PARAMS_NODE_COUNT),
&response);
if (rc < 0) {
printf("Audio: Error reading sub-node info %d.\n", nid);
return rc;
}
*num_sub_nodes = AUDIO_NODE_NUM_SUB_NODES(response);
*start_sub_node_nid = AUDIO_NODE_FIRST_SUB_NODE(response);
return 0;
}
/* Searches the audio group for a node that supports beeping. */
static uint32_t find_beep_node_in_group(uint32_t base, uint32_t group_nid)
{
int rc;
uint32_t node_count = 0;
uint32_t current_nid = 0;
uint32_t end_nid;
uint32_t response;
rc = get_subnode_info(base, group_nid, &node_count, &current_nid);
if (rc < 0)
return 0;
end_nid = current_nid + node_count;
while (current_nid < end_nid) {
rc = exec_one_verb(base,
HDA_VERB(current_nid,
HDA_VERB_GET_PARAMS,
GET_PARAMS_AUDIO_WIDGET_CAPS),
&response);
if (rc < 0) {
printf("Audio: Error reading widget caps.\n");
return 0;
}
if(AUDIO_WIDGET_TYPE(response) == AUDIO_WIDGET_TYPE_BEEP)
return current_nid;
current_nid++;
}
return 0; // No beep node found.
}
/* Checks if the given audio group contains a beep generator. */
static int audio_group_has_beep_node(uint32_t base, uint32_t nid)
{
int rc;
uint32_t response;
rc = exec_one_verb(base,
HDA_VERB(nid,
HDA_VERB_GET_PARAMS,
GET_PARAMS_AUDIO_GROUP_CAPS),
&response);
if (rc < 0) {
printf("Audio: Error reading audio group caps %d.\n", nid);
return 0;
}
return !!(response & AUDIO_GROUP_CAPS_BEEP_GEN);
}
/*
* Finds the nid of the beep node if it exists, if not return 0. Starts at the
* root node, for each sub-node checks if the group contains a beep node. If
* the group contains a beep node, polls each node in the group until it is
* found.
*/
static uint32_t get_hda_beep_nid(HdaCodec *codec, uint32_t base)
{
int rc;
uint32_t node_count = 0;
uint32_t current_nid = 0;
uint32_t end_nid;
if (codec->beep_nid_override != -1) {
rc = codec->beep_nid_override;
return rc;
}
rc = get_subnode_info(base, HDA_ROOT_NODE, &node_count, &current_nid);
if (rc < 0)
return rc;
end_nid = current_nid + node_count;
while (current_nid < end_nid) {
if (audio_group_has_beep_node(base, current_nid))
return find_beep_node_in_group(base, current_nid);
current_nid++;
}
return 0; // No beep node found.
}
/* Sets the beep generator with the given divisor. Pass 0 to disable beep. */
static int set_beep_divisor(HdaCodec *codec, uint8_t divider)
{
uint32_t base;
uint32_t beep_nid;
base = get_hda_base();
beep_nid = get_hda_beep_nid(codec, base);
if (beep_nid <= 0) {
printf("Audio: Failed to find a beep-capable node.\n");
return -1;
}
return write_one_verb(base,
HDA_VERB(beep_nid, HDA_VERB_SET_BEEP, divider));
}
static int hda_codec_start(SoundOps *me, uint32_t frequency)
{
uint8_t divider_val;
HdaCodec *codec = container_of(me, HdaCodec, ops);
if (frequency == 0)
divider_val = 0; // off
else if (frequency > BEEP_FREQ_BASE)
divider_val = 1;
else if (frequency < BEEP_FREQ_BASE / 0xFF)
divider_val = 0xff;
else
divider_val = (uint8_t)(0xFF & (BEEP_FREQ_BASE / frequency));
return set_beep_divisor(codec, divider_val);
}
static int hda_codec_stop(SoundOps *me)
{
HdaCodec *codec = container_of(me, HdaCodec, ops);
return set_beep_divisor(codec, 0);
}
static int hda_codec_play(SoundOps *me, uint32_t msec, uint32_t frequency)
{
int res = sound_start(frequency);
mdelay(msec);
res |= sound_stop();
return res;
}
HdaCodec *new_hda_codec(void)
{
HdaCodec *codec = xzalloc(sizeof(*codec));
codec->ops.start = &hda_codec_start;
codec->ops.stop = &hda_codec_stop;
codec->ops.play = &hda_codec_play;
codec->beep_nid_override = -1;
return codec;
}
void set_hda_beep_nid_override(HdaCodec *codec, int nid)
{
codec->beep_nid_override = nid;
}