drivers/i2c/cs35l53: Add driver for generating device in SSDT
This patch is adding support for Cirrus Logic CS35l41/CS35l53
smart amplifier. This part is now used in number of new chromebook's
HW designs by several vendors.
This driver uses the ACPI Device Property interface to generate
the required parameters into the _DSD table format expected by
the kernel. For detailed information about these properties, please
check Linux kernel documentation:
/Documentation/devicetree/bindings/sound/cirrus,cs35l41.yaml
Change-Id: I2cbb1cef89f8d56ee73fab06c68933a2ab8c3606
Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
Signed-off-by: Vitaly Rodionov <vitaly.rodionov@cirrus.corp-partner.google.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/61448
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Tim Wawrzynczak <twawrzynczak@chromium.org>
diff --git a/src/drivers/i2c/cs35l53/Kconfig b/src/drivers/i2c/cs35l53/Kconfig
new file mode 100644
index 0000000..38d6617
--- /dev/null
+++ b/src/drivers/i2c/cs35l53/Kconfig
@@ -0,0 +1,3 @@
+config DRIVERS_I2C_CS35L53
+ bool
+ depends on HAVE_ACPI_TABLES
diff --git a/src/drivers/i2c/cs35l53/Makefile.inc b/src/drivers/i2c/cs35l53/Makefile.inc
new file mode 100644
index 0000000..21a749f
--- /dev/null
+++ b/src/drivers/i2c/cs35l53/Makefile.inc
@@ -0,0 +1 @@
+ramstage-$(CONFIG_DRIVERS_I2C_CS35L53) += cs35l53.c
diff --git a/src/drivers/i2c/cs35l53/chip.h b/src/drivers/i2c/cs35l53/chip.h
new file mode 100644
index 0000000..9aeb874
--- /dev/null
+++ b/src/drivers/i2c/cs35l53/chip.h
@@ -0,0 +1,149 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <acpi/acpi_device.h>
+
+#define CS35L53_MAX_GPIOS 2
+
+enum cs35l53_boost_type {
+ INTERNAL_BOOST = 0,
+ EXTERNAL_BOOST = 1,
+};
+
+enum cs35l53_boost_ind_nanohenrys {
+ BOOST_IND_1000_NH = 1000,
+ BOOST_IND_1200_NH = 1200,
+ BOOST_IND_1500_NH = 1500,
+ BOOST_IND_2200_NH = 2200,
+};
+
+enum cs35l53_asp_sdout_hiz {
+ ASP_SDOUT_LOGIC0_UNUSED_LOGIC0_DISABLED = 0,
+ ASP_SDOUT_HIZ_UNUSED_LOGIC0_DISABLED = 1,
+ ASP_SDOUT_LOGIC0_UNUSED_HIZ_DISABLED = 2,
+ ASP_SDOUT_HIZ_UNUSED_HIZ_DISABLED = 3,
+};
+
+enum cs35l53_gpio1_src {
+ GPIO1_SRC_HIGH_IMPEDANCE = 0,
+ GPIO1_SRC_GPIO = 1,
+ GPIO1_SRC_SYNC = 2,
+ GPIO1_SRC_MCLK_INPUT = 3,
+};
+
+enum cs35l53_gpio2_src {
+ GPIO2_SRC_HIGH_IMPEDANCE = 0,
+ GPIO2_SRC_GPIO = 1,
+ GPIO2_SRC_OPEN_DRAIN = 2,
+ GPIO2_SRC_MCLK_INPUT = 3,
+ GPIO2_SRC_PUSH_PULL_INTB = 4,
+ GPIO2_SRC_PUSH_PULL_INT = 5,
+};
+
+/*
+ * Cirrus Logic CS35L53 Audio Codec devicetree bindings
+ * linux/Documentation/devicetree/bindings/sound/cirrus,cs35l53.yaml
+ */
+struct drivers_i2c_cs35l53_config {
+ const char *name; /* ACPI Device Name */
+
+ const char *sub; /* SUB ID to uniquely identify system */
+
+ /* 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;
+
+ /* I2C Bus Frequency in Hertz (default 400kHz) */
+ unsigned int bus_speed;
+
+ /* Define cs35l53 parameters */
+ /*
+ * cirrus,boost-type : Configures the type of Boost being used.
+ * Internal boost requires boost-peak-milliamp, boost-ind-nanohenry and
+ * boost-cap-microfarad.
+ * External Boost must have GPIO1 as GPIO output. GPIO1 will be set high to
+ * enable boost voltage.
+ */
+ enum cs35l53_boost_type boost_type;
+
+ /*
+ * cirrus,boost-peak-milliamp : Boost-converter peak current limit in mA.
+ * Configures the peak current by monitoring the current through the boost FET.
+ * Range starts at 1600 mA and goes to a maximum of 4500 mA with increments
+ * of 50 mA. See section 4.3.6 of the datasheet for details.
+ */
+ unsigned int boost_peak_milliamp;
+
+ /*
+ * cirrus,boost-ind-nanohenry : Boost inductor value, expressed in nH. Valid
+ * values include 1000, 1200, 1500 and 2200.
+ */
+ enum cs35l53_boost_ind_nanohenrys boost_ind_nanohenry;
+
+ /*
+ * cirrus,boost-cap-microfarad : Total equivalent boost capacitance on the VBST
+ * and VAMP pins, derated at 11 volts DC. The value must be rounded to the
+ * nearest integer and expressed in uF.
+ */
+ unsigned int boost_cap_microfarad;
+
+ /*
+ * cirrus,asp-sdout-hiz : Audio serial port SDOUT Hi-Z control. Sets the Hi-Z
+ * configuration for SDOUT pin of amplifier.
+ * 0 = Logic 0 during unused slots, and while all transmit channels disabled
+ * 1 = Hi-Z during unused slots but logic 0 while all transmit channels disabled
+ * 2 = Logic 0 during unused slots, but Hi-Z while all transmit channels disabled
+ * 3 = Hi-Z during unused slots and while all transmit channels disabled
+ */
+ enum cs35l53_asp_sdout_hiz asp_sdout_hiz;
+
+ /*
+ * cirrus,gpio1-polarity-invert : Boolean which specifies whether the GPIO1
+ * level is inverted.
+ */
+ bool gpio1_polarity_invert;
+
+ /*
+ * cirrus,gpio2-polarity-invert : Boolean which specifies whether the GPIO2
+ * level is inverted.
+ */
+ bool gpio2_polarity_invert;
+
+ /*
+ * cirrus,gpio1-output-enable : Boolean which specifies whether the GPIO1 pin
+ * is configured as an output.
+ */
+ bool gpio1_output_enable;
+
+ /*
+ * cirrus,gpio2-output-enable : Boolean which specifies whether the GPIO2 pin
+ * is configured as an output.
+ */
+ bool gpio2_output_enable;
+
+ /*
+ * cirrus,gpio1-src-select : Configures the function of the GPIO1 pin.
+ * GPIO1:
+ * 0 = High Impedance (Default)
+ * 1 = GPIO
+ * 2 = Sync
+ * 3 = MCLK input
+ */
+ enum cs35l53_gpio1_src gpio1_src_select;
+
+ /*
+ * cirrus,gpio2-src-select : Configures the function of the GPIO2 pin.
+ * GPIO2:
+ * 0 = High Impedance (Default)
+ * 1 = GPIO
+ * 2 = Open Drain INTB
+ * 3 = MCLK input
+ * 4 = Push-pull INTB (active low)
+ * 5 = Push-pull INT (active high)
+ */
+ enum cs35l53_gpio2_src gpio2_src_select;
+};
diff --git a/src/drivers/i2c/cs35l53/cs35l53.c b/src/drivers/i2c/cs35l53/cs35l53.c
new file mode 100644
index 0000000..1909bef
--- /dev/null
+++ b/src/drivers/i2c/cs35l53/cs35l53.c
@@ -0,0 +1,147 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <acpi/acpi.h>
+#include <acpi/acpi_device.h>
+#include <acpi/acpigen.h>
+#include <console/console.h>
+#include <device/i2c_simple.h>
+#include <device/device.h>
+#include <device/path.h>
+
+#include "chip.h"
+
+#define CS35L53_ACPI_HID "CSC3541"
+
+static void cs35l53_fill_ssdt(const struct device *dev)
+{
+ struct drivers_i2c_cs35l53_config *config = dev->chip_info;
+ const char *scope = acpi_device_scope(dev);
+ const char *path = acpi_device_path(dev);
+ struct acpi_i2c i2c = {
+ .address = dev->path.i2c.device,
+ .mode_10bit = dev->path.i2c.mode_10bit,
+ .speed = config->bus_speed ? : I2C_SPEED_FAST,
+ .resource = scope,
+ };
+ struct acpi_dp *dsd;
+ int gpio_index = 0;
+
+ if (!scope)
+ return;
+
+ /* Device */
+ acpigen_write_scope(scope);
+ acpigen_write_device(acpi_device_name(dev));
+ acpigen_write_name_string("_HID", CS35L53_ACPI_HID);
+ acpigen_write_name_integer("_UID", 0);
+ acpigen_write_name_string("_DDN", dev->chip_ops->name);
+ acpigen_write_name_string("_SUB", config->sub);
+ acpigen_write_STA(acpi_device_status(dev));
+
+ /* Resources */
+ acpigen_write_name("_CRS");
+ acpigen_write_resourcetemplate_header();
+ acpi_device_write_i2c(&i2c);
+ /* 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 cs35l53 reset gpio */
+ if (config->reset_gpio.pin_count)
+ acpi_device_write_gpio(&config->reset_gpio);
+
+ acpigen_write_resourcetemplate_footer();
+
+ /* Add Child Device Properties */
+ 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,boost-type", config->boost_type);
+
+ switch (config->boost_type) {
+ case INTERNAL_BOOST:
+ if ((config->boost_peak_milliamp > 4500) ||
+ (config->boost_peak_milliamp < 1600) ||
+ (config->boost_peak_milliamp % 50)) {
+ printk(BIOS_ERR,
+ "%s: Incorrect boost_peak_milliamp(%d). Using default of 4500 mA\n",
+ __func__, config->boost_peak_milliamp);
+ config->boost_peak_milliamp = 4500;
+ }
+ acpi_dp_add_integer(dsd, "cirrus,boost-peak-milliamp",
+ config->boost_peak_milliamp);
+ acpi_dp_add_integer(dsd, "cirrus,boost-ind-nanohenry",
+ config->boost_ind_nanohenry);
+ acpi_dp_add_integer(dsd, "cirrus,boost-cap-microfarad",
+ config->boost_cap_microfarad);
+ break;
+ case EXTERNAL_BOOST:
+ config->gpio1_output_enable = true;
+ config->gpio1_src_select = GPIO1_SRC_GPIO;
+ break;
+ default:
+ break;
+ }
+
+ acpi_dp_add_integer(dsd, "cirrus,asp-sdout-hiz", config->asp_sdout_hiz);
+ acpi_dp_add_integer(dsd, "cirrus,gpio1-polarity-invert",
+ config->gpio1_polarity_invert);
+ acpi_dp_add_integer(dsd, "cirrus,gpio1-output-enable",
+ config->gpio1_output_enable);
+ acpi_dp_add_integer(dsd, "cirrus,gpio1-src-select", config->gpio1_src_select);
+ acpi_dp_add_integer(dsd, "cirrus,gpio2-polarity-invert",
+ config->gpio2_polarity_invert);
+ acpi_dp_add_integer(dsd, "cirrus,gpio2-output-enable",
+ config->gpio2_output_enable);
+ acpi_dp_add_integer(dsd, "cirrus,gpio2-src-select", config->gpio2_src_select);
+
+ /* Write Device Property Hierarchy */
+ acpi_dp_write(dsd);
+
+ acpigen_pop_len(); /* Device */
+ acpigen_pop_len(); /* Scope */
+
+ printk(BIOS_INFO, "%s: %s address 0%xh irq %d\n",
+ acpi_device_path(dev), dev->chip_ops->name,
+ dev->path.i2c.device, config->irq.pin);
+}
+
+static const char *cs35l53_acpi_name(const struct device *dev)
+{
+ struct drivers_i2c_cs35l53_config *config = dev->chip_info;
+ static char name[ACPI_NAME_BUFFER_SIZE];
+
+ if (config->name)
+ return config->name;
+
+ snprintf(name, sizeof(name), "D%03.3X", dev->path.i2c.device);
+ return name;
+}
+
+static struct device_operations cs35l53_ops = {
+ .read_resources = noop_read_resources,
+ .set_resources = noop_set_resources,
+ .acpi_name = cs35l53_acpi_name,
+ .acpi_fill_ssdt = cs35l53_fill_ssdt,
+};
+
+static void cs35l53_enable(struct device *dev)
+{
+ dev->ops = &cs35l53_ops;
+}
+
+struct chip_operations drivers_i2c_cs35l53_ops = {
+ CHIP_NAME("Cirrus Logic CS35L53 Audio Codec")
+ .enable_dev = cs35l53_enable
+};