Newer
Older
minerva / Kernel / Devices / Audio / IntelHDA / Format.cpp
@minerva minerva on 13 Jul 4 KB Initial commit
/*
 * Copyright (c) 2023, Jelle Raaijmakers <jelle@gmta.nl>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include "Format.h"
#include <AK/Array.h>

namespace Kernel::Audio::IntelHDA {

// 3.3.41: Input/Output/Bidirectional Stream Descriptor Format
// 3.7.1: Stream Format Structure
struct SampleRateParameters {
    u32 sample_rate;
    u8 base;
    u8 multiple;
    u8 divisor;
};
static constexpr Array<SampleRateParameters, 15> sample_rate_parameters { {
    // clang-format off
    { 6'000,   0b0, 0b000, 0b111 },
    { 8'000,   0b0, 0b000, 0b101 },
    { 9'600,   0b0, 0b000, 0b100 },
    { 11'025,  0b1, 0b000, 0b011 },
    { 16'000,  0b0, 0b000, 0b010 },
    { 22'050,  0b1, 0b000, 0b001 },
    { 24'000,  0b0, 0b000, 0b001 },
    { 32'000,  0b0, 0b001, 0b010 },
    { 44'100,  0b1, 0b000, 0b000 },
    { 48'000,  0b0, 0b000, 0b000 },
    { 88'200,  0b1, 0b001, 0b000 },
    { 96'000,  0b0, 0b001, 0b000 },
    { 144'000, 0b0, 0b010, 0b000 },
    { 176'400, 0b1, 0b011, 0b000 },
    { 192'000, 0b0, 0b011, 0b000 },
    // clang-format on
} };

struct PcmBitsParameters {
    u8 pcm_bits;
    u8 encoding;
};
static constexpr Array<PcmBitsParameters, 5> pcm_bits_parameters { {
    // clang-format off
    { 8,  0b000 },
    { 16, 0b001 },
    { 20, 0b010 },
    { 24, 0b011 },
    { 32, 0b100 },
    // clang-format on
} };

ErrorOr<u16> encode_format(FormatParameters format)
{
    // 3.3.41: Input/Output/Bidirectional Stream Descriptor Format
    // 3.7.1: Stream Format Structure

    // Stream type
    // NOTE: we only support PCM streams
    auto is_pcm = true;

    // Sample rate parameters
    Optional<SampleRateParameters> selected_sample_rate {};
    for (auto sample_rate_parameter : sample_rate_parameters) {
        if (sample_rate_parameter.sample_rate == format.sample_rate) {
            selected_sample_rate = sample_rate_parameter;
            break;
        }
    }
    if (!selected_sample_rate.has_value())
        return ENOTSUP;

    // Bit size
    Optional<PcmBitsParameters> selected_bit_rate {};
    for (auto pcm_bits_parameter : pcm_bits_parameters) {
        if (pcm_bits_parameter.pcm_bits == format.pcm_bits) {
            selected_bit_rate = pcm_bits_parameter;
            break;
        }
    }
    if (!selected_bit_rate.has_value())
        return ENOTSUP;

    // Number of channels
    if (format.number_of_channels < 1 || format.number_of_channels > 16)
        return ENOTSUP;

    // Construct stream format
    return ((is_pcm ? 0 : 1) << 15)
        | ((selected_sample_rate->base & 0x1) << 14)
        | ((selected_sample_rate->multiple & 0x7) << 11)
        | ((selected_sample_rate->divisor & 0x7) << 8)
        | ((selected_bit_rate->encoding & 0x7) << 4)
        | ((format.number_of_channels - 1) & 0xf);
}

ErrorOr<FormatParameters> decode_format(u16 format)
{
    // 3.3.41: Input/Output/Bidirectional Stream Descriptor Format
    // 3.7.1: Stream Format Structure

    // Sample rate
    u8 sample_rate_base = (format >> 14) & 0x1;
    u8 sample_rate_multiple = (format >> 11) & 0x7;
    u8 sample_rate_divisor = (format >> 8) & 0x7;
    Optional<SampleRateParameters> found_sample_rate {};
    for (auto sample_rate_parameter : sample_rate_parameters) {
        if (sample_rate_parameter.base == sample_rate_base
            && sample_rate_parameter.multiple == sample_rate_multiple
            && sample_rate_parameter.divisor == sample_rate_divisor) {
            found_sample_rate = sample_rate_parameter;
            break;
        }
    }

    // PCM bits
    u8 pcm_bits = (format >> 4) & 0x7;
    Optional<PcmBitsParameters> found_pcm_bits {};
    for (auto pcm_bits_parameter : pcm_bits_parameters) {
        if (pcm_bits_parameter.encoding == pcm_bits) {
            found_pcm_bits = pcm_bits_parameter;
            break;
        }
    }

    // Number of channels
    u8 number_of_channels = (format & 0xf) + 1;

    if (!found_sample_rate.has_value() || !found_pcm_bits.has_value())
        return EINVAL;

    return FormatParameters {
        .sample_rate = found_sample_rate.release_value().sample_rate,
        .pcm_bits = found_pcm_bits.release_value().pcm_bits,
        .number_of_channels = number_of_channels,
    };
}

}