#include <sys/note.h>
#include <sys/conf.h>
#include <sys/kmem.h>
#include <sys/debug.h>
#include <sys/vtrace.h>
#include <sys/varargs.h>
#include <sys/ddi_impldefs.h>
#include <sys/pci.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/pci_impl.h>
#include <sys/pcie_acpi.h>
#include <sys/hotplug/pci/pcie_hp.h>
#include <sys/hotplug/pci/pciehpc.h>
#include <sys/hotplug/pci/pciehpc_acpi.h>
static int pciehpc_acpi_hpc_init(pcie_hp_ctrl_t *ctrl_p);
static int pciehpc_acpi_hpc_uninit(pcie_hp_ctrl_t *ctrl_p);
static int pciehpc_acpi_slotinfo_init(pcie_hp_ctrl_t *ctrl_p);
static int pciehpc_acpi_slotinfo_uninit(pcie_hp_ctrl_t *ctrl_p);
static int pciehpc_acpi_enable_intr(pcie_hp_ctrl_t *ctrl_p);
static int pciehpc_acpi_disable_intr(pcie_hp_ctrl_t *ctrl_p);
static int pciehpc_acpi_slot_poweron(pcie_hp_slot_t *slot_p,
ddi_hp_cn_state_t *result);
static int pciehpc_acpi_slot_poweroff(pcie_hp_slot_t *slot_p,
ddi_hp_cn_state_t *result);
static void pciehpc_acpi_setup_ops(pcie_hp_ctrl_t *ctrl_p);
static ACPI_STATUS pciehpc_acpi_install_event_handler(pcie_hp_ctrl_t *ctrl_p);
static void pciehpc_acpi_uninstall_event_handler(pcie_hp_ctrl_t *ctrl_p);
static ACPI_STATUS pciehpc_acpi_power_on_slot(pcie_hp_ctrl_t *ctrl_p);
static ACPI_STATUS pciehpc_acpi_power_off_slot(pcie_hp_ctrl_t *ctrl_p);
static void pciehpc_acpi_notify_handler(ACPI_HANDLE device, uint32_t val,
void *context);
static ACPI_STATUS pciehpc_acpi_get_dev_state(ACPI_HANDLE obj, int *statusp);
void
pciehpc_update_ops(pcie_hp_ctrl_t *ctrl_p)
{
boolean_t hp_native_mode = B_FALSE;
uint32_t osc_flags = OSC_CONTROL_PCIE_NAT_HP;
if (pcie_acpi_osc(ctrl_p->hc_dip, &osc_flags) == DDI_SUCCESS) {
hp_native_mode = (osc_flags & OSC_CONTROL_PCIE_NAT_HP) ?
B_TRUE : B_FALSE;
}
if (!hp_native_mode) {
pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
pciehpc_acpi_setup_ops(ctrl_p);
bus_p->bus_hp_sup_modes |= PCIE_ACPI_HP_MODE;
bus_p->bus_hp_curr_mode = PCIE_ACPI_HP_MODE;
}
}
void
pciehpc_acpi_setup_ops(pcie_hp_ctrl_t *ctrl_p)
{
ctrl_p->hc_ops.init_hpc_hw = pciehpc_acpi_hpc_init;
ctrl_p->hc_ops.uninit_hpc_hw = pciehpc_acpi_hpc_uninit;
ctrl_p->hc_ops.init_hpc_slotinfo = pciehpc_acpi_slotinfo_init;
ctrl_p->hc_ops.uninit_hpc_slotinfo = pciehpc_acpi_slotinfo_uninit;
ctrl_p->hc_ops.poweron_hpc_slot = pciehpc_acpi_slot_poweron;
ctrl_p->hc_ops.poweroff_hpc_slot = pciehpc_acpi_slot_poweroff;
ctrl_p->hc_ops.disable_hpc_intr = pciehpc_acpi_disable_intr;
ctrl_p->hc_ops.enable_hpc_intr = pciehpc_acpi_enable_intr;
}
static int
pciehpc_acpi_hpc_init(pcie_hp_ctrl_t *ctrl_p)
{
ACPI_HANDLE pcibus_obj;
int status = AE_ERROR;
ACPI_HANDLE slot_dev_obj;
ACPI_HANDLE hdl;
pciehpc_acpi_t *acpi_p;
uint16_t bus_methods = 0;
uint16_t slot_methods = 0;
status = acpica_get_handle(ctrl_p->hc_dip, &pcibus_obj);
if (status != AE_OK)
return (DDI_FAILURE);
status = AcpiGetNextObject(ACPI_TYPE_DEVICE, pcibus_obj,
NULL, &slot_dev_obj);
if (status != AE_OK) {
PCIE_DBG("pciehpc_acpi_hpc_init: Get ACPI object failed\n");
return (DDI_FAILURE);
}
if (AcpiGetHandle(pcibus_obj, "_OSC", &hdl) == AE_OK)
bus_methods |= PCIEHPC_ACPI_OSC_PRESENT;
if (AcpiGetHandle(pcibus_obj, "_OSHP", &hdl) == AE_OK)
bus_methods |= PCIEHPC_ACPI_OSHP_PRESENT;
if (AcpiGetHandle(pcibus_obj, "_HPX", &hdl) == AE_OK)
bus_methods |= PCIEHPC_ACPI_HPX_PRESENT;
if (AcpiGetHandle(pcibus_obj, "_HPP", &hdl) == AE_OK)
bus_methods |= PCIEHPC_ACPI_HPP_PRESENT;
if (AcpiGetHandle(pcibus_obj, "_DSM", &hdl) == AE_OK)
bus_methods |= PCIEHPC_ACPI_DSM_PRESENT;
if (AcpiGetHandle(slot_dev_obj, "_SUN", &hdl) == AE_OK)
slot_methods |= PCIEHPC_ACPI_SUN_PRESENT;
if (AcpiGetHandle(slot_dev_obj, "_PS0", &hdl) == AE_OK)
slot_methods |= PCIEHPC_ACPI_PS0_PRESENT;
if (AcpiGetHandle(slot_dev_obj, "_EJ0", &hdl) == AE_OK)
slot_methods |= PCIEHPC_ACPI_EJ0_PRESENT;
if (AcpiGetHandle(slot_dev_obj, "_STA", &hdl) == AE_OK)
slot_methods |= PCIEHPC_ACPI_STA_PRESENT;
acpi_p = kmem_zalloc(sizeof (pciehpc_acpi_t), KM_SLEEP);
acpi_p->bus_obj = pcibus_obj;
acpi_p->slot_dev_obj = slot_dev_obj;
acpi_p->bus_methods = bus_methods;
acpi_p->slot_methods = slot_methods;
ctrl_p->hc_misc_data = acpi_p;
return (DDI_SUCCESS);
}
static int
pciehpc_acpi_hpc_uninit(pcie_hp_ctrl_t *ctrl_p)
{
if (ctrl_p->hc_misc_data) {
kmem_free(ctrl_p->hc_misc_data, sizeof (pciehpc_acpi_t));
ctrl_p->hc_misc_data = NULL;
}
return (DDI_SUCCESS);
}
static int
pciehpc_acpi_enable_intr(pcie_hp_ctrl_t *ctrl_p)
{
return (DDI_SUCCESS);
}
static int
pciehpc_acpi_disable_intr(pcie_hp_ctrl_t *ctrl_p)
{
return (DDI_SUCCESS);
}
static int
pciehpc_acpi_slotinfo_init(pcie_hp_ctrl_t *ctrl_p)
{
uint32_t slot_capabilities;
pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0];
pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
mutex_enter(&ctrl_p->hc_mutex);
slot_p->hs_device_num = 0;
slot_p->hs_info.cn_type = DDI_HP_CN_TYPE_PCIE;
slot_p->hs_info.cn_type_str = PCIE_ACPI_HP_TYPE;
slot_p->hs_info.cn_child = NULL;
slot_p->hs_minor =
PCI_MINOR_NUM(ddi_get_instance(ctrl_p->hc_dip),
slot_p->hs_device_num);
slot_capabilities = pciehpc_reg_get32(ctrl_p,
bus_p->bus_pcie_off + PCIE_SLOTCAP);
pciehpc_set_slot_name(ctrl_p);
ctrl_p->hc_has_attn = (slot_capabilities &
PCIE_SLOTCAP_ATTN_BUTTON) ? B_TRUE : B_FALSE;
ctrl_p->hc_has_mrl = (slot_capabilities & PCIE_SLOTCAP_MRL_SENSOR) ?
B_TRUE : B_FALSE;
ctrl_p->hc_has_emi_lock = (slot_capabilities &
PCIE_SLOTCAP_EMI_LOCK_PRESENT) ? B_TRUE : B_FALSE;
pciehpc_led_init(slot_p);
pciehpc_get_slot_state(slot_p);
if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_ENABLED)
slot_p->hs_condition = AP_COND_OK;
mutex_exit(&ctrl_p->hc_mutex);
if (!pciehpc_slot_kstat_init(slot_p)) {
(void) pciehpc_acpi_slotinfo_uninit(ctrl_p);
return (DDI_FAILURE);
}
if (pciehpc_acpi_install_event_handler(ctrl_p) != AE_OK) {
(void) pciehpc_acpi_slotinfo_uninit(ctrl_p);
return (DDI_FAILURE);
}
PCIE_DBG("ACPI hot plug is enabled for slot #%d\n",
slot_p->hs_phy_slot_num);
return (DDI_SUCCESS);
}
static int
pciehpc_acpi_slotinfo_uninit(pcie_hp_ctrl_t *ctrl_p)
{
pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0];
pciehpc_acpi_uninstall_event_handler(ctrl_p);
if (slot_p->hs_info.cn_name)
kmem_free(slot_p->hs_info.cn_name,
strlen(slot_p->hs_info.cn_name) + 1);
pciehpc_slot_kstat_fini(slot_p);
return (DDI_SUCCESS);
}
static int
pciehpc_acpi_slot_poweron(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result)
{
pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl;
pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
uint16_t status, control;
ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex));
pciehpc_get_slot_state(slot_p);
if (slot_p->hs_info.cn_state == DDI_HP_CN_STATE_ENABLED) {
PCIE_DBG("slot %d already connected\n",
slot_p->hs_phy_slot_num);
*result = slot_p->hs_info.cn_state;
return (DDI_SUCCESS);
}
status = pciehpc_reg_get16(ctrl_p,
bus_p->bus_pcie_off + PCIE_SLOTSTS);
if ((ctrl_p->hc_has_mrl) && (status & PCIE_SLOTSTS_MRL_SENSOR_OPEN)) {
cmn_err(CE_WARN, "MRL switch is open on slot %d",
slot_p->hs_phy_slot_num);
goto cleanup;
}
if (!(status & PCIE_SLOTSTS_PRESENCE_DETECTED)) {
PCIE_DBG("slot %d is empty\n", slot_p->hs_phy_slot_num);
goto cleanup;
}
control = pciehpc_reg_get16(ctrl_p,
bus_p->bus_pcie_off + PCIE_SLOTCTL);
if (!(control & PCIE_SLOTCTL_PWR_CONTROL)) {
PCIE_DBG("slot %d already connected\n",
slot_p->hs_phy_slot_num);
*result = slot_p->hs_info.cn_state;
return (DDI_SUCCESS);
}
if (pciehpc_acpi_power_on_slot(ctrl_p) != AE_OK)
goto cleanup;
*result = slot_p->hs_info.cn_state = DDI_HP_CN_STATE_POWERED;
return (DDI_SUCCESS);
cleanup:
return (DDI_FAILURE);
}
static int
pciehpc_acpi_slot_poweroff(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result)
{
pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl;
pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
uint16_t status;
ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex));
pciehpc_get_slot_state(slot_p);
if (slot_p->hs_info.cn_state < DDI_HP_CN_STATE_POWERED) {
PCIE_DBG("slot %d already disconnected\n",
slot_p->hs_phy_slot_num);
*result = slot_p->hs_info.cn_state;
return (DDI_SUCCESS);
}
status = pciehpc_reg_get16(ctrl_p,
bus_p->bus_pcie_off + PCIE_SLOTSTS);
if (!(status & PCIE_SLOTSTS_PRESENCE_DETECTED)) {
PCIE_DBG("slot %d is empty", slot_p->hs_phy_slot_num);
goto cleanup;
}
if (pciehpc_acpi_power_off_slot(ctrl_p) != AE_OK)
goto cleanup;
pciehpc_get_slot_state(slot_p);
*result = slot_p->hs_info.cn_state;
return (DDI_SUCCESS);
cleanup:
return (DDI_FAILURE);
}
static ACPI_STATUS
pciehpc_acpi_install_event_handler(pcie_hp_ctrl_t *ctrl_p)
{
pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0];
int status = AE_OK;
pciehpc_acpi_t *acpi_p;
PCIE_DBG("install event handler for slot %d\n",
slot_p->hs_phy_slot_num);
acpi_p = ctrl_p->hc_misc_data;
if (acpi_p->slot_dev_obj == NULL)
return (AE_NOT_FOUND);
status = AcpiInstallNotifyHandler(acpi_p->slot_dev_obj,
ACPI_SYSTEM_NOTIFY, pciehpc_acpi_notify_handler, (void *)ctrl_p);
if (status != AE_OK)
goto cleanup;
status = AcpiInstallNotifyHandler(acpi_p->bus_obj,
ACPI_SYSTEM_NOTIFY, pciehpc_acpi_notify_handler, (void *)ctrl_p);
return (status);
cleanup:
(void) AcpiRemoveNotifyHandler(acpi_p->slot_dev_obj,
ACPI_SYSTEM_NOTIFY, pciehpc_acpi_notify_handler);
return (status);
}
static void
pciehpc_acpi_notify_handler(ACPI_HANDLE device, uint32_t val, void *context)
{
pcie_hp_ctrl_t *ctrl_p = context;
pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0];
pciehpc_acpi_t *acpi_p;
ddi_hp_cn_state_t curr_state;
int dev_state = 0;
PCIE_DBG("received Notify(%d) event on slot #%d\n",
val, slot_p->hs_phy_slot_num);
mutex_enter(&ctrl_p->hc_mutex);
acpi_p = ctrl_p->hc_misc_data;
if (pciehpc_acpi_get_dev_state(acpi_p->slot_dev_obj,
&dev_state) != AE_OK) {
cmn_err(CE_WARN, "failed to get device status on slot %d",
slot_p->hs_phy_slot_num);
}
PCIE_DBG("(1)device state on slot #%d: 0x%x\n",
slot_p->hs_phy_slot_num, dev_state);
curr_state = slot_p->hs_info.cn_state;
pciehpc_get_slot_state(slot_p);
switch (val) {
case 0:
case 3:
{
ddi_hp_cn_state_t target_state;
if (ctrl_p->hc_has_attn == B_FALSE) {
PCIE_DBG("Ignore the unexpected event "
"on slot #%d (state 0x%x)",
slot_p->hs_phy_slot_num, dev_state);
break;
}
if (curr_state < DDI_HP_CN_STATE_POWERED) {
target_state = DDI_HP_CN_STATE_ENABLED;
if (slot_p->hs_info.cn_state == DDI_HP_CN_STATE_ENABLED)
slot_p->hs_info.cn_state =
DDI_HP_CN_STATE_POWERED;
} else {
target_state = DDI_HP_CN_STATE_EMPTY;
}
(void) ndi_hp_state_change_req(slot_p->hs_ctrl->hc_dip,
slot_p->hs_info.cn_name,
target_state, DDI_HP_REQ_ASYNC);
break;
}
default:
cmn_err(CE_NOTE, "Unknown Notify() event %d on slot #%d\n",
val, slot_p->hs_phy_slot_num);
break;
}
mutex_exit(&ctrl_p->hc_mutex);
}
static void
pciehpc_acpi_uninstall_event_handler(pcie_hp_ctrl_t *ctrl_p)
{
pciehpc_acpi_t *acpi_p = ctrl_p->hc_misc_data;
pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0];
PCIE_DBG("Uninstall event handler for slot #%d\n",
slot_p->hs_phy_slot_num);
(void) AcpiRemoveNotifyHandler(acpi_p->slot_dev_obj,
ACPI_SYSTEM_NOTIFY, pciehpc_acpi_notify_handler);
(void) AcpiRemoveNotifyHandler(acpi_p->bus_obj,
ACPI_SYSTEM_NOTIFY, pciehpc_acpi_notify_handler);
}
static ACPI_STATUS
pciehpc_acpi_power_on_slot(pcie_hp_ctrl_t *ctrl_p)
{
int status = AE_OK;
pciehpc_acpi_t *acpi_p = ctrl_p->hc_misc_data;
int dev_state = 0;
pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0];
PCIE_DBG("turn ON power to the slot #%d\n", slot_p->hs_phy_slot_num);
status = AcpiEvaluateObject(acpi_p->slot_dev_obj, "_PS0", NULL, NULL);
if (status == AE_OK) {
if (pciehpc_acpi_get_dev_state(acpi_p->slot_dev_obj,
&dev_state) != AE_OK)
cmn_err(CE_WARN, "failed to get device status "
"on slot #%d", slot_p->hs_phy_slot_num);
}
PCIE_DBG("(3)device state on slot #%d: 0x%x\n",
slot_p->hs_phy_slot_num, dev_state);
pciehpc_get_slot_state(slot_p);
if (slot_p->hs_info.cn_state < DDI_HP_CN_STATE_POWERED) {
cmn_err(CE_WARN, "failed to power on the slot #%d"
"(dev_state 0x%x, ACPI_STATUS 0x%x)",
slot_p->hs_phy_slot_num, dev_state, status);
return (AE_ERROR);
}
return (status);
}
static ACPI_STATUS
pciehpc_acpi_power_off_slot(pcie_hp_ctrl_t *ctrl_p)
{
int status = AE_OK;
pciehpc_acpi_t *acpi_p = ctrl_p->hc_misc_data;
int dev_state = 0;
pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0];
PCIE_DBG("turn OFF power to the slot #%d\n", slot_p->hs_phy_slot_num);
status = AcpiEvaluateObject(acpi_p->slot_dev_obj, "_EJ0", NULL, NULL);
if (status == AE_OK) {
if (pciehpc_acpi_get_dev_state(acpi_p->slot_dev_obj,
&dev_state) != AE_OK)
cmn_err(CE_WARN, "failed to get device status "
"on slot #%d", slot_p->hs_phy_slot_num);
}
PCIE_DBG("(2)device state on slot #%d: 0x%x\n",
slot_p->hs_phy_slot_num, dev_state);
pciehpc_get_slot_state(slot_p);
if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_POWERED) {
cmn_err(CE_WARN, "failed to power OFF the slot #%d"
"(dev_state 0x%x, ACPI_STATUS 0x%x)",
slot_p->hs_phy_slot_num, dev_state, status);
return (AE_ERROR);
}
return (status);
}
static ACPI_STATUS
pciehpc_acpi_get_dev_state(ACPI_HANDLE obj, int *statusp)
{
int status;
ACPI_STATUS ret;
ret = acpica_get_object_status(obj, &status);
if (ACPI_SUCCESS(ret)) {
*statusp = status;
}
return (ret);
}