// SPDX-License-Identifier: GPL-2.0+ /* * Qualcomm generic pmic gpio driver * * (C) Copyright 2015 Mateusz Kulikowski * (C) Copyright 2023 Linaro Ltd. */ #include #include #include #include #include #include #include #include #include #define REG_TYPE 0x4 #define REG_SUBTYPE 0x5 struct qcom_pmic_btn_data { char *compatible; unsigned int status_bit; int code; char *label; }; struct qcom_pmic_btn_priv { u32 base; u32 status_bit; int code; struct udevice *pmic; }; #define PON_INT_RT_STS 0x10 #define PON_KPDPWR_N_SET 0 #define PON_RESIN_N_SET 1 #define PON_GEN3_RESIN_N_SET 6 #define PON_GEN3_KPDPWR_N_SET 7 static enum button_state_t qcom_pwrkey_get_state(struct udevice *dev) { struct qcom_pmic_btn_priv *priv = dev_get_priv(dev); int reg = pmic_reg_read(priv->pmic, priv->base + PON_INT_RT_STS); if (reg < 0) return 0; return (reg & BIT(priv->status_bit)) != 0; } static int qcom_pwrkey_get_code(struct udevice *dev) { struct qcom_pmic_btn_priv *priv = dev_get_priv(dev); return priv->code; } static const struct qcom_pmic_btn_data qcom_pmic_btn_data_table[] = { { .compatible = "qcom,pm8941-pwrkey", .status_bit = PON_KPDPWR_N_SET, .code = KEY_ENTER, .label = "pwrkey", }, { .compatible = "qcom,pm8941-resin", .status_bit = PON_RESIN_N_SET, .code = KEY_DOWN, .label = "vol_down", }, { .compatible = "qcom,pmk8350-pwrkey", .status_bit = PON_GEN3_KPDPWR_N_SET, .code = KEY_ENTER, .label = "pwrkey", }, { .compatible = "qcom,pmk8350-resin", .status_bit = PON_GEN3_RESIN_N_SET, .code = KEY_DOWN, .label = "vol_down", }, }; static const struct qcom_pmic_btn_data *button_qcom_pmic_match(ofnode node) { int i; for (i = 0; i < ARRAY_SIZE(qcom_pmic_btn_data_table); ++i) { if (ofnode_device_is_compatible(node, qcom_pmic_btn_data_table[i].compatible)) return &qcom_pmic_btn_data_table[i]; } return NULL; } static int qcom_pwrkey_probe(struct udevice *dev) { struct button_uc_plat *uc_plat = dev_get_uclass_plat(dev); struct qcom_pmic_btn_priv *priv = dev_get_priv(dev); const struct qcom_pmic_btn_data *btn_data; ofnode node = dev_ofnode(dev); int ret; u64 base; /* Ignore the top-level pon node */ if (!uc_plat->label) return 0; /* Get the data for the node compatible */ btn_data = button_qcom_pmic_match(node); if (!btn_data) return -EINVAL; priv->status_bit = btn_data->status_bit; priv->code = btn_data->code; /* the pwrkey and resin nodes are children of the "pon" node, get the * PMIC device to use in pmic_reg_* calls. */ priv->pmic = dev->parent->parent; /* Get the address of the parent pon node */ base = dev_read_addr(dev->parent); if (base == FDT_ADDR_T_NONE) { printf("%s: Can't find address\n", dev->name); return -EINVAL; } priv->base = base; /* Do a sanity check */ ret = pmic_reg_read(priv->pmic, priv->base + REG_TYPE); if (ret != 0x1 && ret != 0xb) { printf("%s: unexpected PMIC function type %d\n", dev->name, ret); return -ENXIO; } ret = pmic_reg_read(priv->pmic, priv->base + REG_SUBTYPE); if (ret < 0 || (ret & 0x7) == 0) { printf("%s: unexpected PMIC function subtype %d\n", dev->name, ret); return -ENXIO; } return 0; } static int button_qcom_pmic_bind(struct udevice *parent) { struct udevice *dev; ofnode node; int ret; dev_for_each_subnode(node, parent) { const struct qcom_pmic_btn_data *btn_data; struct button_uc_plat *uc_plat; const char *label; if (!ofnode_is_enabled(node)) continue; /* Get the data for the node compatible */ btn_data = button_qcom_pmic_match(node); if (!btn_data) { debug("Unknown button node '%s'\n", ofnode_get_name(node)); continue; } ret = device_bind_driver_to_node(parent, "qcom_pwrkey", ofnode_get_name(node), node, &dev); if (ret) { printf("Failed to bind %s! %d\n", label, ret); return ret; } uc_plat = dev_get_uclass_plat(dev); uc_plat->label = btn_data->label; } return 0; } static const struct button_ops button_qcom_pmic_ops = { .get_state = qcom_pwrkey_get_state, .get_code = qcom_pwrkey_get_code, }; static const struct udevice_id qcom_pwrkey_ids[] = { { .compatible = "qcom,pm8916-pon" }, { .compatible = "qcom,pm8941-pon" }, { .compatible = "qcom,pm8998-pon" }, { .compatible = "qcom,pmk8350-pon" }, { } }; U_BOOT_DRIVER(qcom_pwrkey) = { .name = "qcom_pwrkey", .id = UCLASS_BUTTON, .of_match = qcom_pwrkey_ids, .bind = button_qcom_pmic_bind, .probe = qcom_pwrkey_probe, .ops = &button_qcom_pmic_ops, .priv_auto = sizeof(struct qcom_pmic_btn_priv), };