| /* |
| * 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 <pci.h> |
| #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/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, ¤t_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, ¤t_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; |
| } |