blob: 08f672f7c25cfa8806044abeaaa41468715968f7 [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <acpi/acpigen.h>
#include <acpi/acpi_device.h>
#include <gpio.h>
#include <stdio.h>
#include "chip.h"
#include "soc/intel/common/block/include/intelblocks/acpi.h"
#include "soc/intel/common/block/pcie/rtd3/chip.h"
/* FCPO# to RESET# delay time during WWAN ON */
#define FM350GL_TN2B 20
/* RESET# to PERST# delay time during WWAN ON */
#define FM350GL_TB2R 80
/* The delay between de-assertion of PERST# to change of PDS state from 0 to 1 during WWAN ON */
#define FM350GL_TR2P 0
/* RESET# to FCPO# delay time during WWAN OFF */
#define FM350GL_TB2F 10
/* Time to allow the WWAN module to fully discharge any residual voltages before FCPO# could be
de-asserted again. */
#define FM350GL_TFDI 500
/* The delay between assertion and de-assertion RESET# during FLDR */
#define FM350GL_TBTG 10
/* The delay between de-assertion of RESET# and change of PDS state from 0 to 1 after FLDR */
#define FM350GL_TBTP 170
/* PERST# to RESET# delay time during WWAN OFF */
#define FM350GL_TR2B 10
/* 20s HW initialization needed after de-assertion of PERST#
However, it is not required and is not proper place to ensure HW initialization in ACPI. The
delay here is to ensure the following reset or RTD3 _OFF method won't be called immediately.
*/
#define FM350GL_TIME_HW_INIT 100
enum reset_type {
RESET_TYPE_WARM = 0,
RESET_TYPE_COLD = 1
};
/*
* Returns the RTD3 PM methods requested and available to the device.
*/
static enum acpi_pcie_rp_pm_emit
wwan_fm350gl_get_rtd3_method_support(const struct drivers_wwan_fm_config *config)
{
const struct soc_intel_common_block_pcie_rtd3_config *rtd3_config;
rtd3_config = config_of(config->rtd3dev);
return rtd3_config->ext_pm_support;
}
/*
* Generate first half reset flow (FHRF) method.
* Arg0 = 0; RESET_TYPE_WARM: warm reset
* Arg0 = 1; RESET_TYPE_COLD: cold reset
*/
static void wwan_fm350gl_acpi_method_fhrf(const struct device *parent_dev,
const struct drivers_wwan_fm_config *config)
{
acpigen_write_method_serialized("FHRF", 1);
{
char mutex_path[128];
const struct soc_intel_common_block_pcie_rtd3_config *rtd3_config;
rtd3_config = config_of(config->rtd3dev);
if (rtd3_config->use_rp_mutex) {
snprintf(mutex_path, sizeof(mutex_path), "%s",
acpi_device_path_join(parent_dev, RP_MUTEX_NAME));
/* Acquire root port mutex in case FHRF is called directly and not called from _RST */
acpigen_write_acquire(mutex_path, ACPI_MUTEX_NO_TIMEOUT);
}
/* LOCAL0 = PERST# */
acpigen_get_tx_gpio(&config->perst_gpio);
acpigen_write_if_lequal_op_int(LOCAL0_OP, 0);
{
if (wwan_fm350gl_get_rtd3_method_support(config) &
ACPI_PCIE_RP_EMIT_L23) {
acpigen_emit_namestring(acpi_device_path_join(parent_dev,
"DL23"));
}
/* assert PERST# pin */
acpigen_enable_tx_gpio(&config->perst_gpio);
}
acpigen_write_if_end(); /* If */
acpigen_write_sleep(FM350GL_TR2B);
/* assert RESET# pin */
acpigen_enable_tx_gpio(&config->reset_gpio);
/* warm reset */
acpigen_write_if_lequal_op_int(ARG0_OP, RESET_TYPE_WARM);
{
acpigen_write_sleep(FM350GL_TBTG);
}
/* cold reset */
acpigen_write_else();
{
acpigen_write_if_lequal_op_int(ARG0_OP, RESET_TYPE_COLD);
{
/* disable source clock */
if (wwan_fm350gl_get_rtd3_method_support(config) &
ACPI_PCIE_RP_EMIT_SRCK) {
acpigen_emit_namestring(acpi_device_path_join(
parent_dev, "SRCK"));
acpigen_emit_byte(ZERO_OP);
}
acpigen_write_sleep(FM350GL_TB2F);
/* assert FCPO# pin */
acpigen_enable_tx_gpio(&config->fcpo_gpio);
acpigen_write_sleep(FM350GL_TFDI);
}
acpigen_write_if_end(); /* If */
}
acpigen_pop_len(); /* Else */
if (rtd3_config->use_rp_mutex)
acpigen_write_release(mutex_path);
}
acpigen_write_method_end(); /* Method */
}
/*
* Generate second half reset flow (SHRF) method.
*/
static void wwan_fm350gl_acpi_method_shrf(const struct device *parent_dev,
const struct drivers_wwan_fm_config *config)
{
acpigen_write_method_serialized("SHRF", 0);
{
char mutex_path[128];
const struct soc_intel_common_block_pcie_rtd3_config *rtd3_config;
rtd3_config = config_of(config->rtd3dev);
if (rtd3_config->use_rp_mutex) {
snprintf(mutex_path, sizeof(mutex_path), "%s",
acpi_device_path_join(parent_dev, RP_MUTEX_NAME));
/* Acquire root port mutex */
acpigen_write_acquire(mutex_path, ACPI_MUTEX_NO_TIMEOUT);
}
/* call rtd3 method to Disable ModPHY Power Gating. */
if (wwan_fm350gl_get_rtd3_method_support(config) &
ACPI_PCIE_RP_EMIT_PSD0) {
acpigen_emit_namestring(acpi_device_path_join(parent_dev,
"PSD0"));
}
/* call rtd3 method to Enable SRC Clock. */
if (wwan_fm350gl_get_rtd3_method_support(config) &
ACPI_PCIE_RP_EMIT_SRCK) {
acpigen_emit_namestring(acpi_device_path_join(parent_dev,
"SRCK"));
acpigen_emit_byte(ONE_OP);
}
/* De-assert FCPO# GPIO. */
acpigen_disable_tx_gpio(&config->fcpo_gpio);
acpigen_write_sleep(FM350GL_TN2B);
/* De-assert RESET# GPIO. */
acpigen_disable_tx_gpio(&config->reset_gpio);
acpigen_write_sleep(FM350GL_TB2R);
/* De-assert PERST# GPIO. */
acpigen_disable_tx_gpio(&config->perst_gpio);
/* Call rtd3 method to trigger L2/L3 ready exit flow in root port */
if (wwan_fm350gl_get_rtd3_method_support(config) &
ACPI_PCIE_RP_EMIT_L23) {
acpigen_emit_namestring(acpi_device_path_join(parent_dev,
"L23D"));
}
acpigen_write_sleep(FM350GL_TIME_HW_INIT);
if (rtd3_config->use_rp_mutex)
acpigen_write_release(mutex_path);
}
acpigen_write_method_end(); /* Method */
}
/*
* Generate _RST method. This is to perform a soft reset. It is added under
* PXSX. This is called during device driver removal.
*/
static void wwan_fm350gl_acpi_method_rst(const struct device *parent_dev,
const struct drivers_wwan_fm_config *config)
{
acpigen_write_method_serialized("_RST", 0);
{
char mutex_path[128];
const struct soc_intel_common_block_pcie_rtd3_config *rtd3_config;
rtd3_config = config_of(config->rtd3dev);
if (rtd3_config->use_rp_mutex) {
snprintf(mutex_path, sizeof(mutex_path), "%s",
acpi_device_path_join(parent_dev, RP_MUTEX_NAME));
/* Acquire root port mutex */
acpigen_write_acquire(mutex_path, ACPI_MUTEX_NO_TIMEOUT);
}
/* Perform 1st Half of FLDR Flow for soft reset: FHRF(0) */
acpigen_emit_namestring("FHRF");
acpigen_emit_byte(RESET_TYPE_WARM);
/* Perform 2nd Half of FLDR Flow: SHRF() */
acpigen_emit_namestring("SHRF");
/* Indicates that the following _Off will be skipped. */
acpigen_emit_byte(INCREMENT_OP);
acpigen_emit_namestring(acpi_device_path_join(parent_dev, "RTD3.OFSK"));
if (rtd3_config->use_rp_mutex)
acpigen_write_release(mutex_path);
}
acpigen_write_method_end(); /* Method */
}
/*
* Generate _RST method. This is to perform a cold reset. This reset will be
* included under PXSX.MRST. This method is used during device firmware update.
*/
static void wwan_fm350gl_acpi_method_mrst_rst(const struct device *parent_dev,
const struct drivers_wwan_fm_config *config)
{
acpigen_write_method_serialized("_RST", 0);
{
char mutex_path[128];
const struct soc_intel_common_block_pcie_rtd3_config *rtd3_config;
rtd3_config = config_of(config->rtd3dev);
if (rtd3_config->use_rp_mutex) {
snprintf(mutex_path, sizeof(mutex_path), "%s",
acpi_device_path_join(parent_dev, RP_MUTEX_NAME));
/* Acquire root port mutex */
acpigen_write_acquire(mutex_path, ACPI_MUTEX_NO_TIMEOUT);
}
/* Perform 1st Half of FLDR Flow for cold reset: FHRF (1) */
acpigen_emit_namestring("FHRF");
acpigen_emit_byte(RESET_TYPE_COLD);
/* Perform 2nd Half of FLDR Flow: SHRF () */
acpigen_emit_namestring("SHRF");
/* Indicate kernel ACPI PM to skip _off RTD3 after reset at the end of
driver removal */
acpigen_emit_byte(INCREMENT_OP);
acpigen_emit_namestring(acpi_device_path_join(parent_dev, "RTD3.OFSK"));
if (rtd3_config->use_rp_mutex)
acpigen_write_release(mutex_path);
}
acpigen_write_method_end(); /* Method */
}
/*
* Generate DPTS (Device Prepare To Seep) Method. This is called in
* \.SB.MPTS Method.
*/
static void wwan_fm350gl_acpi_method_dpts(const struct device *parent_dev,
const struct drivers_wwan_fm_config *config)
{
acpigen_write_method_serialized("DPTS", 1);
{
/* Perform 1st Half of FLDR Flow for cold reset: FHRF (1) */
acpigen_emit_namestring("FHRF");
acpigen_emit_byte(RESET_TYPE_COLD);
}
acpigen_write_method_end(); /* Method */
}
static const char *wwan_fm350gl_acpi_name(const struct device *dev)
{
/* Attached device name must be "PXSX" for the Linux Kernel to recognize it. */
return "PXSX";
}
static void
wwan_fm350gl_acpi_event_interrupts(const struct acpi_gpio *wake_gpio)
{
acpigen_write_name("_AEI");
acpigen_write_resourcetemplate_header();
acpi_device_write_gpio(wake_gpio);
acpigen_write_resourcetemplate_footer();
}
static void
wwan_fm350gl_acpi_event_method(const struct device *dev,
const struct acpi_gpio *wake_gpio)
{
char name[5];
uint16_t pin;
pin = wake_gpio->pins[0];
if (CONFIG(GENERIC_GPIO_LIB))
pin = gpio_acpi_pin(pin);
if (pin > 0xff) {
printk(BIOS_ERR, "%s: pins above 0xFF are unsupported (pin %u)\n",
__func__, pin);
return;
}
snprintf(name, sizeof(name), "_%c%02X",
wake_gpio->irq.mode == ACPI_IRQ_EDGE_TRIGGERED ? 'E' : 'L', pin);
acpigen_write_method_serialized(name, 0);
acpigen_notify(acpi_device_path(dev), 0x02); /* NOTIFY_DEVICE_WAKE */
acpigen_write_method_end();
}
static void wwan_fm350gl_acpi_gpio_events(const struct device *dev)
{
const struct drivers_wwan_fm_config *config = config_of(dev);
const struct acpi_gpio *wake_gpio = &config->wake_gpio;
/* Write into GPIO controller's scope */
if (CONFIG(GENERIC_GPIO_LIB))
acpigen_write_scope(wake_gpio->resource ? : gpio_acpi_path(wake_gpio->pins[0]));
else
acpigen_write_scope(wake_gpio->resource);
wwan_fm350gl_acpi_event_interrupts(wake_gpio);
wwan_fm350gl_acpi_event_method(dev, wake_gpio);
acpigen_write_scope_end();
}
static void wwan_fm350gl_acpi_fill_ssdt(const struct device *dev)
{
const struct drivers_wwan_fm_config *config = config_of(dev);
const struct device *parent = dev->bus->dev;
const char *scope = acpi_device_path(parent);
const struct soc_intel_common_block_pcie_rtd3_config *rtd3_config;
if (!is_dev_enabled(parent)) {
printk(BIOS_ERR, "%s: root port not enabled\n", __func__);
return;
}
if (!scope) {
printk(BIOS_ERR, "%s: root port scope not found\n", __func__);
return;
}
if (!config->fcpo_gpio.pin_count && !config->reset_gpio.pin_count &&
!config->perst_gpio.pin_count) {
printk(BIOS_ERR, "%s: FCPO, RESET, PERST GPIO required for %s.\n",
__func__, scope);
return;
}
rtd3_config = config_of(config->rtd3dev);
if (!rtd3_config->use_rp_mutex)
printk(BIOS_WARNING, "%s: RTD3 must use root port mutex.\n",
__func__);
printk(BIOS_INFO, "%s: Enable WWAN for %s (%s)\n", scope, dev_path(parent),
config->desc ?: dev->chip_ops->name);
acpigen_write_scope(scope);
{
acpigen_write_device(wwan_fm350gl_acpi_name(dev));
{
acpigen_write_ADR(0);
if (config->name)
acpigen_write_name_string("_DDN", config->name);
if (config->desc)
acpigen_write_name_unicode("_STR", config->desc);
wwan_fm350gl_acpi_method_fhrf(parent, config);
wwan_fm350gl_acpi_method_shrf(parent, config);
wwan_fm350gl_acpi_method_rst(parent, config);
wwan_fm350gl_acpi_method_dpts(parent, config);
if (config->add_acpi_dma_property)
acpi_device_add_dma_property(NULL);
/* NOTE: the 5G driver will call MRST._RST to trigger a cold reset
* during firmware update.
*/
acpigen_write_device("MRST");
{
acpigen_write_ADR(0);
wwan_fm350gl_acpi_method_mrst_rst(parent, config);
}
acpigen_write_device_end(); /* Device */
}
acpigen_write_device_end(); /* Device */
}
acpigen_write_scope_end(); /* Scope */
if (config->wake_gpio.pin_count && config->wake_gpio.type == ACPI_GPIO_TYPE_INTERRUPT)
wwan_fm350gl_acpi_gpio_events(dev);
}
static struct device_operations wwan_fm350gl_ops = {
.read_resources = noop_read_resources,
.set_resources = noop_set_resources,
.acpi_fill_ssdt = wwan_fm350gl_acpi_fill_ssdt,
.acpi_name = wwan_fm350gl_acpi_name
};
static void wwan_fm350gl_acpi_enable(struct device *dev)
{
dev->ops = &wwan_fm350gl_ops;
}
struct chip_operations drivers_wwan_fm_ops = {
CHIP_NAME("Fibocom FM-350-GL")
.enable_dev = wwan_fm350gl_acpi_enable
};