drivers/soundwire/cs42l42: Support CS42L42 SoundWire device

The CS42L42 low power audio codec can be connected over SoundWire and
be configured for mainboards to use:

- Data Port 0 and Bulk Register Access
- Data Port 1 is the 64bit data output for the headset
- Data Port 2 is the 64bit data input for the headset
- Data Port 3 is the 64bit data input for the headset

The data port and audio mode properties are filled out as best as
possible with the datasheet as a reference.

The ACPI address for the codec is calculated with the information in
the codec driver combined with the devicetree.cb hierarchy where the
link and unique IDs are extracted from the device path.

For example this device is connected to master link ID 0 and has strap
settings configuring it for unique ID 0.

chip drivers/soundwire/cs42l42
  register "desc" = ""Headset Codec""
  device generic 0.0 on end
end

This driver was tested with the rex0 reference design by booting
and disassembling the runtime SSDT to ensure that the devices have the
expected address and properties.

Device (SW00)
{
  Name (_ADR, 0x00001001FA424200)  // _ADR: Address
  Name (_DDN, "Headset Codec")  // _DDN: DOS Device Name
  Method (_STA, 0, NotSerialized)  // _STA: Status
  {
    Return (0x0F)
  }
  Name (_CRS, ResourceTemplate ()  // _CRS: Current Resource Settings
  {
    GpioInt (Edge, ActiveBoth, Exclusive, PullDefault, 0x0000,
      "\\_SB.PCI0.GPIO", 0x00, ResourceConsumer, ,
      )
      {   // Pin list
        0x0166
      }
    GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly,
      "\\_SB.PCI0.GPIO", 0x00, ResourceConsumer, ,
      )
      {   // Pin list
        0x0167
      }
  })

  Name (_DSD, Package ()
  {
    ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
    Package ()
    {
      Package () { "irq-gpios",
        Package () { \_SB.PCI0.HDAS.SNDW.SW00, Zero, Zero, Zero }
      },
      Package () { "reset-gpios",
        Package () { \_SB.PCI0.HDAS.SNDW.SW00, One, Zero, Zero }
      },
      Package () { "cirrus,ts-inv", One },
      Package () { "cirrus,ts-dbnc-rise", 0x05 },
      Package () { "cirrus,ts-dbnc-fall", Zero },
      Package () { "cirrus,btn-det-init-dbnce", 0x64 },
      Package () { "cirrus,btn-det-event-dbnce", 0x0A },
      Package () { "cirrus,bias-lvls",
        Package () { 0x0F, 0x08, 0x04, One }
      },
      Package () { "cirrus,hs-bias-ramp-rate", 0x02 },
      Package () { "cirrus,hs-bias-sense-disable", One },
      Package () { "mipi-sdw-sw-interface-revision", 0x00010000 },
      [...]
      Package () { "mipi-sdw-source-port-list", 0x02 },
      Package () { "mipi-sdw-sink-port-list", 0x0C }
    },

    ToUUID ("dbb8e3e6-5886-4ba6-8795-1319f52a966b"),
    Package ()
    {
      Package () { "mipi-sdw-port-bra-mode-0", "BRA0" },
      Package () { "mipi-sdw-dp-0-subproperties", "DP0" },
      Package () { "mipi-sdw-port-audio-mode-0", "MOD0" },
      Package () { "mipi-sdw-dp-1-source-subproperties", "SRC1" },
      Package () { "mipi-sdw-dp-2-sink-subproperties", "SNK2" },
      Package () { "mipi-sdw-dp-3-sink-subproperties", "SNK3"}
    }
  })
  Name (BRA0, Package ()
  {
    ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
    Package ()
    {
      Package () { "mipi-sdw-bra-mode-bus-frequency-configs",
        Package () { 0x00AC4400, ... }
      },
      Package () { "mipi-sdw-bra-mode-max-data-per-frame", 0x1000 },
      Package () { "mipi-sdw-bra-mode-min-us-between-transactions", Zero }
    }
  })
  Name (DP0, Package ()
  {
    ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
    Package ()
    {
      Package () { "mipi-sdw-port-max-wordlength", 0x40 },
      [...]
    },

    ToUUID ("dbb8e3e6-5886-4ba6-8795-1319f52a966b"),
    Package ()
    {
      Package () { "mipi-sdw-port-bra-mode-0", "BRA0" }
    }
  })
  Name (MOD0, Package ()
  {
    ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
    Package ()
    {
      Package () { "mipi-sdw-audio-mode-bus-frequency-configs",
        Package () { 0x00AC4400, ... }
      },
      Package () { "mipi-sdw-audio-mode-max-sampling-frequency", 0x0002EE00 },
      Package () { "mipi-sdw-audio-mode-min-sampling-frequency", 0x1F40 },
      [...]
    }
  })
  Name (SRC1, Package ()
  {
    ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
    Package ()
    {
      Package () { "mipi-sdw-data-port-type", Zero },
      [...]
    },

    ToUUID ("dbb8e3e6-5886-4ba6-8795-1319f52a966b"),
    Package ()
    {
      Package () { "mipi-sdw-port-audio-mode-0", "MOD0" }
    }
  })
  Name (SNK2, Package ()
  {
    ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
    Package ()
    {
      Package () { "mipi-sdw-data-port-type", Zero },
      [...]
    },

    ToUUID ("dbb8e3e6-5886-4ba6-8795-1319f52a966b"),
    Package ()
    {
      Package () { "mipi-sdw-port-audio-mode-0", "MOD0" }
    }
  })
  Name (SNK3, Package ()
  {
    ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
    Package ()
    {
      Package () { "mipi-sdw-data-port-type", Zero },
      [...]
    },

    ToUUID ("dbb8e3e6-5886-4ba6-8795-1319f52a966b"),
    Package ()
    {
      Package () { "mipi-sdw-port-audio-mode-0", "MOD0" }
    }
  })
}

BUG=b:269497731
TEST=Verified SSDT for SNDW in the OS

Signed-off-by: Kapil Porwal <kapilporwal@google.com>
Change-Id: Ic7cfe2a21c76ba01ad3dea2a5017b28743aeb9f1
Reviewed-on: https://review.coreboot.org/c/coreboot/+/73279
Reviewed-by: Subrata Banik <subratabanik@google.com>
Reviewed-by: Sridhar Siricilla <sridhar.siricilla@intel.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
This commit is contained in:
Kapil Porwal 2023-02-25 22:23:01 +05:30 committed by Felix Held
parent 1fe5fcf30d
commit 93b7fd1d4d
5 changed files with 357 additions and 0 deletions

View File

@ -0,0 +1,6 @@
config DRIVERS_SOUNDWIRE_CS42L42
bool
depends on HAVE_ACPI_TABLES
default n
help
SoundWire CS42L42 audio codec SSDT generator.

View File

@ -0,0 +1 @@
ramstage-$(CONFIG_DRIVERS_SOUNDWIRE_CS42L42) += cs42l42.c

View File

@ -0,0 +1,116 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __DRIVERS_SOUNDWIRE_CS42L42_CHIP_H__
#define __DRIVERS_SOUNDWIRE_CS42L42_CHIP_H__
#include <acpi/acpi.h>
#include<acpi/acpi_device.h>
enum cs42l42_ts_dbnc_rise {
RISE_DEB_0_MS = 0,
RISE_DEB_125_MS = 1,
RISE_DEB_250_MS = 2,
RISE_DEB_500_MS = 3,
RISE_DEB_750_MS = 4,
RISE_DEB_1000_MS = 5,
RISE_DEB_1250_MS = 6,
RISE_DEB_1500_MS = 7,
};
enum cs42l42_ts_dbnc_fall {
FALL_DEB_0_MS = 0,
FALL_DEB_125_MS = 1,
FALL_DEB_250_MS = 2,
FALL_DEB_500_MS = 3,
FALL_DEB_750_MS = 4,
FALL_DEB_1000_MS = 5,
FALL_DEB_1250_MS = 6,
FALL_DEB_1500_MS = 7,
};
enum cs42l42_hs_bias_ramp_rate {
HSBIAS_RAMP_FAST_RISE_SLOW_FALL = 0,
HSBIAS_RAMP_FAST = 1,
HSBIAS_RAMP_SLOW = 2,
HSBIAS_RAMP_SLOWEST = 3,
};
struct drivers_soundwire_cs42l42_config {
char acpi_name[ACPI_NAME_BUFFER_SIZE]; /* Set by the acpi_name ops */
const char *desc;
/* Interrupt configuration */
struct acpi_irq irq;
/* Use GPIO based interrupt instead of PIRQ */
struct acpi_gpio irq_gpio;
/* Use GPIO based reset gpio */
struct acpi_gpio reset_gpio;
/* Define cs42L42 parameters */
/*
* cirrus,ts-inv : Boolean property. For jacks that invert the tip sense
* polarity. Normal jacks will short tip sense pin to HS1 when headphones are
* plugged in and leave tip sense floating when not plugged in. Inverting jacks
* short tip sense when unplugged and float when plugged in.
* false = Non-inverted
* true = Inverted
* Default = Non-inverted
*/
bool ts_inv;
/*
* cirrus,ts-dbnc-rise : Debounce the rising edge of TIP_SENSE_PLUG. With no
* debounce, the tip sense pin might be noisy on a plug event.
* Default = RISE_DEB_1000_MS
*/
enum cs42l42_ts_dbnc_rise ts_dbnc_rise;
/*
* cirrus,ts-dbnc-fall : Debounce the falling edge of TIP_SENSE_UNPLUG.
* With no debounce, the tip sense pin might be noisy on an unplug event.
* Default = FALL_DEB_1000_MS
*/
enum cs42l42_ts_dbnc_fall ts_dbnc_fall;
/*
* cirrus,btn-det-init-dbnce : This sets how long the driver sleeps after
* enabling button detection interrupts. After auto-detection and before
* servicing button interrupts, the HS bias needs time to settle. If you
* don't wait, there is possibility for erroneous button interrupt.
* Value in ms, 0 - 200.
* Default = 100ms
*/
unsigned int btn_det_init_dbnce;
/*
* cirrus,btn-det-event-dbnce : This sets how long the driver delays after
* receiving a button press interrupt. With level detect interrupts, you want
* to wait a small amount of time to make sure the button press is making a
* clean connection with the bias resistors.
* Value in ms, 0 - 20.
* Default = 10ms
*/
unsigned int btn_det_event_dbnce;
/*
* cirrus,bias-lvls : For a level-detect headset button scheme, each button
* will bias the mic pin to a certain voltage. To determine which button was
* pressed, the driver will compare this biased voltage to sequential,
* decreasing voltages and will stop when a comparator is tripped,
* indicating a comparator voltage < bias voltage. This value represents a
* percentage of the internally generated HS bias voltage. For different
* hardware setups, a designer might want to tweak this. This is an array of
* descending values for the comparator voltage.
* Array of 4 values
* Each 0-63
* < x1 x2 x3 x4 >
* Default = < 15 8 4 1>
*/
uint64_t bias_lvls[4];
/* headset bias ramp rate */
enum cs42l42_hs_bias_ramp_rate hs_bias_ramp_rate;
/*
* cirrus,hs-bias-sense-disable: This is boolean property. If present the
* HSBIAS sense is disabled. Configures HSBIAS output current sense through
* the external 2.21-k resistor. HSBIAS_SENSE is hardware feature to reduce
* the potential pop noise during the headset plug out slowly. But on some
* platforms ESD voltage will affect it causing test to fail, especially
* with CTIA headset type. For different hardware setups, a designer might
* want to tweak default behavior.
*/
bool hs_bias_sense_disable;
};
#endif /* __DRIVERS_SOUNDWIRE_CS42L42_CHIP_H__ */

View File

@ -0,0 +1,231 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <acpi/acpigen.h>
#include <acpi/acpi_device.h>
#include <acpi/acpi_soundwire.h>
#include <device/device.h>
#include <device/path.h>
#include <device/soundwire.h>
#include <mipi/ids.h>
#include <stdio.h>
#include "chip.h"
static struct soundwire_address cs42l42_address = {
.version = SOUNDWIRE_VERSION_1_0,
.manufacturer_id = MIPI_MFG_ID_CIRRUS,
.part_id = MIPI_DEV_ID_CIRRUS_CS42L42,
.class = MIPI_CLASS_NONE
};
static struct soundwire_slave cs42l42_slave = {
.wake_up_unavailable = false,
.test_mode_supported = false,
.clock_stop_mode1_supported = true,
.simplified_clockstopprepare_sm_supported = false,
.clockstopprepare_hard_reset_behavior = true,
.highPHY_capable = false,
.paging_supported = true,
.bank_delay_supported = true,
.port15_read_behavior = false,
.source_port_list = SOUNDWIRE_PORT(1),
.sink_port_list = SOUNDWIRE_PORT(2) | SOUNDWIRE_PORT(3)
};
static struct soundwire_bra_mode cs42l42_dp0_bra_mode = {
.bus_frequency_configs_count = 6,
.bus_frequency_configs = {
11289600, /* 11.2896 MHz */
12000 * KHz, /* 12 MHz */
12288 * KHz, /* 12.288 MHz */
22579200, /* 22.5792 MHz */
24000 * KHz, /* 24 MHz */
24576 * KHz, /* 24.576 MHz */
},
.max_data_per_frame = 4096, /* MaxRow*MaxCol = 256*16 = 4096 */
.min_us_between_transactions = 0
};
static struct soundwire_dp0 cs42l42_dp0 = {
.port_max_wordlength = 64,
.port_min_wordlength = 1,
.bra_imp_def_response_supported = false,
.simplified_channel_prepare_sm = true,
.imp_def_dp0_interrupts_supported = 0,
.imp_def_bpt_supported = true,
.bra_mode_count = 1,
.bra_mode_list = { 0 }
};
static struct soundwire_audio_mode cs42l42_audio_mode = {
.bus_frequency_configs_count = 6,
.bus_frequency_configs = {
11289600, /* 11.2896 MHz */
12000 * KHz, /* 12 MHz */
12288 * KHz, /* 12.288 MHz */
22579200, /* 22.5792 MHz */
24000 * KHz, /* 24 MHz */
24576 * KHz, /* 24.576 MHz */
},
/* Support 8 KHz to 192 KHz sampling frequency */
.max_sampling_frequency = 192 * KHz,
.min_sampling_frequency = 8 * KHz,
.prepare_channel_behavior = CHANNEL_PREPARE_ANY_FREQUENCY
};
static struct soundwire_dpn cs42l42_dp1 = {
.port_max_wordlength = 64,
.port_min_wordlength = 1,
.data_port_type = FULL_DATA_PORT,
.max_grouping_supported = BLOCK_GROUP_COUNT_1,
.simplified_channelprepare_sm = false,
.imp_def_dpn_interrupts_supported = 0,
.min_channel_number = 1,
.max_channel_number = 1,
.modes_supported = MODE_ISOCHRONOUS | MODE_TX_CONTROLLED |
MODE_RX_CONTROLLED | MODE_FULL_ASYNCHRONOUS,
.block_packing_mode = true,
.port_audio_mode_count = 1,
.port_audio_mode_list = { 0 }
};
static struct soundwire_dpn cs42l42_dpn = {
.port_max_wordlength = 64,
.port_min_wordlength = 1,
.data_port_type = FULL_DATA_PORT,
.max_grouping_supported = BLOCK_GROUP_COUNT_1,
.simplified_channelprepare_sm = false,
.imp_def_dpn_interrupts_supported = 0,
.min_channel_number = 1,
.max_channel_number = 2,
.modes_supported = MODE_ISOCHRONOUS | MODE_TX_CONTROLLED |
MODE_RX_CONTROLLED | MODE_FULL_ASYNCHRONOUS,
.block_packing_mode = true,
.port_audio_mode_count = 1,
.port_audio_mode_list = { 0 }
};
static const struct soundwire_codec cs42l42_codec = {
.slave = &cs42l42_slave,
.dp0_bra_mode = { &cs42l42_dp0_bra_mode },
.dp0 = &cs42l42_dp0,
.audio_mode = { &cs42l42_audio_mode },
.dpn = {
{
.port = 1,
.source = &cs42l42_dp1,
},
{
.port = 2,
.sink = &cs42l42_dpn,
},
{
.port = 3,
.sink = &cs42l42_dpn,
}
}
};
static void soundwire_cs42l42_fill_ssdt(const struct device *dev)
{
struct drivers_soundwire_cs42l42_config *config = dev->chip_info;
const char *scope = acpi_device_scope(dev);
const char *path = acpi_device_path(dev);
struct acpi_dp *dsd;
int gpio_index = 0;
if (!scope)
return;
acpigen_write_scope(scope);
acpigen_write_device(acpi_device_name(dev));
/* Set codec address IDs. */
cs42l42_address.link_id = dev->path.generic.id;
cs42l42_address.unique_id = dev->path.generic.subid;
acpigen_write_ADR_soundwire_device(&cs42l42_address);
acpigen_write_name_string("_DDN", config->desc ? : dev->chip_ops->name);
acpigen_write_STA(acpi_device_status(dev));
/* Resources */
acpigen_write_name("_CRS");
acpigen_write_resourcetemplate_header();
/* Use either Interrupt() or GpioInt() */
if (config->irq_gpio.pin_count)
acpi_device_write_gpio(&config->irq_gpio);
else
acpi_device_write_interrupt(&config->irq);
/* for cs42l42 reset gpio */
if (config->reset_gpio.pin_count)
acpi_device_write_gpio(&config->reset_gpio);
acpigen_write_resourcetemplate_footer();
dsd = acpi_dp_new_table("_DSD");
if (config->irq_gpio.pin_count)
acpi_dp_add_gpio(dsd, "irq-gpios", path,
gpio_index++, /* Index = 0 */
0, /* Pin = 0 (There is a single pin in the GPIO resource). */
config->irq_gpio.active_low);
if (config->reset_gpio.pin_count)
acpi_dp_add_gpio(dsd, "reset-gpios", path,
gpio_index++, /* Index = 0 or 1 (if irq gpio is written). */
0, /* Pin = 0 (There is a single pin in the GPIO resource). */
config->reset_gpio.active_low);
acpi_dp_add_integer(dsd, "cirrus,ts-inv", config->ts_inv ? 1 : 0);
acpi_dp_add_integer(dsd, "cirrus,ts-dbnc-rise", config->ts_dbnc_rise);
acpi_dp_add_integer(dsd, "cirrus,ts-dbnc-fall", config->ts_dbnc_fall);
acpi_dp_add_integer(dsd, "cirrus,btn-det-init-dbnce", config->btn_det_init_dbnce);
if (config->btn_det_init_dbnce > 200) {
printk(BIOS_ERR, "%s: Incorrect btn_det_init_dbnce(%d). Using default of 100ms\n",
__func__, config->btn_det_init_dbnce);
config->btn_det_init_dbnce = 100;
}
acpi_dp_add_integer(dsd, "cirrus,btn-det-event-dbnce", config->btn_det_event_dbnce);
if (config->btn_det_event_dbnce > 100) {
printk(BIOS_ERR, "%s: Incorrect btn_det_event_dbnce(%d). Using default of 10ms\n",
__func__, config->btn_det_event_dbnce);
config->btn_det_event_dbnce = 10;
}
acpi_dp_add_integer_array(dsd, "cirrus,bias-lvls", config->bias_lvls, 4);
acpi_dp_add_integer(dsd, "cirrus,hs-bias-ramp-rate", config->hs_bias_ramp_rate);
if (config->hs_bias_sense_disable)
acpi_dp_add_integer(dsd, "cirrus,hs-bias-sense-disable", 1);
soundwire_gen_codec(dsd, &cs42l42_codec, NULL);
acpi_dp_write(dsd);
acpigen_pop_len(); /* Device */
acpigen_pop_len(); /* Scope */
}
static const char *soundwire_cs42l42_acpi_name(const struct device *dev)
{
struct drivers_soundwire_cs42l42_config *config = dev->chip_info;
if (config->acpi_name[0] != 0)
return config->acpi_name;
snprintf(config->acpi_name, sizeof(config->acpi_name), "SW%1X%1X",
dev->path.generic.id, dev->path.generic.subid);
return config->acpi_name;
}
static struct device_operations soundwire_cs42l42_ops = {
.read_resources = noop_read_resources,
.set_resources = noop_set_resources,
.acpi_name = soundwire_cs42l42_acpi_name,
.acpi_fill_ssdt = soundwire_cs42l42_fill_ssdt,
};
static void soundwire_cs42l42_enable(struct device *dev)
{
dev->ops = &soundwire_cs42l42_ops;
}
struct chip_operations drivers_soundwire_cs42l42_ops = {
CHIP_NAME("Cirrus Logic CS42L42 SoundWire Codec")
.enable_dev = soundwire_cs42l42_enable
};

View File

@ -27,4 +27,7 @@
#define MIPI_DEV_ID_MAXIM_MAX98373 0x8373 #define MIPI_DEV_ID_MAXIM_MAX98373 0x8373
#define MIPI_DEV_ID_MAXIM_MAX98363 0x8363 #define MIPI_DEV_ID_MAXIM_MAX98363 0x8363
#define MIPI_MFG_ID_CIRRUS 0x01fa
#define MIPI_DEV_ID_CIRRUS_CS42L42 0x4242
#endif /* __MIPI_IDS_H__ */ #endif /* __MIPI_IDS_H__ */