kernel_samsung_a53x/drivers/mmc/core/slot-gpio.c
Hans de Goede 83b588c2ae mmc: core: Add mmc_gpiod_set_cd_config() function
commit 63a7cd660246aa36af263b85c33ecc6601bf04be upstream.

Some mmc host drivers may need to fixup a card-detection GPIO's config
to e.g. enable the GPIO controllers builtin pull-up resistor on devices
where the firmware description of the GPIO is broken (e.g. GpioInt with
PullNone instead of PullUp in ACPI DSDT).

Since this is the exception rather then the rule adding a config
parameter to mmc_gpiod_request_cd() seems undesirable, so instead
add a new mmc_gpiod_set_cd_config() function. This is simply a wrapper
to call gpiod_set_config() on the card-detect GPIO acquired through
mmc_gpiod_request_cd().

Reviewed-by: Andy Shevchenko <andy@kernel.org>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Acked-by: Adrian Hunter <adrian.hunter@intel.com>
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/20240410191639.526324-2-hdegoede@redhat.com
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2024-11-19 12:27:18 +01:00

281 lines
6.8 KiB
C
Executable file

// SPDX-License-Identifier: GPL-2.0-only
/*
* Generic GPIO card-detect helper
*
* Copyright (C) 2011, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
*/
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
#include <linux/mmc/host.h>
#include <linux/mmc/slot-gpio.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <trace/hooks/mmc_core.h>
#include "slot-gpio.h"
struct mmc_gpio {
struct gpio_desc *ro_gpio;
struct gpio_desc *cd_gpio;
irqreturn_t (*cd_gpio_isr)(int irq, void *dev_id);
char *ro_label;
char *cd_label;
u32 cd_debounce_delay_ms;
};
static irqreturn_t mmc_gpio_cd_irqt(int irq, void *dev_id)
{
/* Schedule a card detection after a debounce timeout */
struct mmc_host *host = dev_id;
struct mmc_gpio *ctx = host->slot.handler_priv;
bool allow = true;
trace_android_vh_mmc_gpio_cd_irqt(host, &allow);
if (!allow)
return IRQ_HANDLED;
host->trigger_card_event = true;
mmc_detect_change(host, msecs_to_jiffies(ctx->cd_debounce_delay_ms));
return IRQ_HANDLED;
}
int mmc_gpio_alloc(struct mmc_host *host)
{
struct mmc_gpio *ctx = devm_kzalloc(host->parent,
sizeof(*ctx), GFP_KERNEL);
if (ctx) {
ctx->cd_debounce_delay_ms = 200;
ctx->cd_label = devm_kasprintf(host->parent, GFP_KERNEL,
"%s cd", dev_name(host->parent));
if (!ctx->cd_label)
return -ENOMEM;
ctx->ro_label = devm_kasprintf(host->parent, GFP_KERNEL,
"%s ro", dev_name(host->parent));
if (!ctx->ro_label)
return -ENOMEM;
host->slot.handler_priv = ctx;
host->slot.cd_irq = -EINVAL;
}
return ctx ? 0 : -ENOMEM;
}
int mmc_gpio_get_ro(struct mmc_host *host)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
int cansleep;
if (!ctx || !ctx->ro_gpio)
return -ENOSYS;
cansleep = gpiod_cansleep(ctx->ro_gpio);
return cansleep ?
gpiod_get_value_cansleep(ctx->ro_gpio) :
gpiod_get_value(ctx->ro_gpio);
}
EXPORT_SYMBOL(mmc_gpio_get_ro);
int mmc_gpio_get_cd(struct mmc_host *host)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
int cansleep;
if (!ctx || !ctx->cd_gpio)
return -ENOSYS;
cansleep = gpiod_cansleep(ctx->cd_gpio);
return cansleep ?
gpiod_get_value_cansleep(ctx->cd_gpio) :
gpiod_get_value(ctx->cd_gpio);
}
EXPORT_SYMBOL(mmc_gpio_get_cd);
void mmc_gpiod_request_cd_irq(struct mmc_host *host)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
int irq = -EINVAL;
int ret;
if (host->slot.cd_irq >= 0 || !ctx || !ctx->cd_gpio)
return;
/*
* Do not use IRQ if the platform prefers to poll, e.g., because that
* IRQ number is already used by another unit and cannot be shared.
*/
if (!(host->caps & MMC_CAP_NEEDS_POLL))
irq = gpiod_to_irq(ctx->cd_gpio);
if (irq >= 0) {
if (!ctx->cd_gpio_isr)
ctx->cd_gpio_isr = mmc_gpio_cd_irqt;
ret = devm_request_threaded_irq(host->parent, irq,
NULL, ctx->cd_gpio_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
ctx->cd_label, host);
if (ret < 0)
irq = ret;
}
host->slot.cd_irq = irq;
if (irq < 0)
host->caps |= MMC_CAP_NEEDS_POLL;
}
EXPORT_SYMBOL(mmc_gpiod_request_cd_irq);
int mmc_gpio_set_cd_wake(struct mmc_host *host, bool on)
{
int ret = 0;
if (!(host->caps & MMC_CAP_CD_WAKE) ||
host->slot.cd_irq < 0 ||
on == host->slot.cd_wake_enabled)
return 0;
if (on) {
ret = enable_irq_wake(host->slot.cd_irq);
host->slot.cd_wake_enabled = !ret;
} else {
disable_irq_wake(host->slot.cd_irq);
host->slot.cd_wake_enabled = false;
}
return ret;
}
EXPORT_SYMBOL(mmc_gpio_set_cd_wake);
/* Register an alternate interrupt service routine for
* the card-detect GPIO.
*/
void mmc_gpio_set_cd_isr(struct mmc_host *host,
irqreturn_t (*isr)(int irq, void *dev_id))
{
struct mmc_gpio *ctx = host->slot.handler_priv;
WARN_ON(ctx->cd_gpio_isr);
ctx->cd_gpio_isr = isr;
}
EXPORT_SYMBOL(mmc_gpio_set_cd_isr);
/**
* mmc_gpiod_request_cd - request a gpio descriptor for card-detection
* @host: mmc host
* @con_id: function within the GPIO consumer
* @idx: index of the GPIO to obtain in the consumer
* @override_active_level: ignore %GPIO_ACTIVE_LOW flag
* @debounce: debounce time in microseconds
*
* Note that this must be called prior to mmc_add_host()
* otherwise the caller must also call mmc_gpiod_request_cd_irq().
*
* Returns zero on success, else an error.
*/
int mmc_gpiod_request_cd(struct mmc_host *host, const char *con_id,
unsigned int idx, bool override_active_level,
unsigned int debounce)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
struct gpio_desc *desc;
int ret;
desc = devm_gpiod_get_index(host->parent, con_id, idx, GPIOD_IN);
if (IS_ERR(desc))
return PTR_ERR(desc);
if (debounce) {
ret = gpiod_set_debounce(desc, debounce);
if (ret < 0)
ctx->cd_debounce_delay_ms = debounce / 1000;
}
/* override forces default (active-low) polarity ... */
if (override_active_level && !gpiod_is_active_low(desc))
gpiod_toggle_active_low(desc);
/* ... or active-high */
if (host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH)
gpiod_toggle_active_low(desc);
ctx->cd_gpio = desc;
return 0;
}
EXPORT_SYMBOL(mmc_gpiod_request_cd);
/**
* mmc_gpiod_set_cd_config - set config for card-detection GPIO
* @host: mmc host
* @config: Generic pinconf config (from pinconf_to_config_packed())
*
* This can be used by mmc host drivers to fixup a card-detection GPIO's config
* (e.g. set PIN_CONFIG_BIAS_PULL_UP) after acquiring the GPIO descriptor
* through mmc_gpiod_request_cd().
*
* Returns:
* 0 on success, or a negative errno value on error.
*/
int mmc_gpiod_set_cd_config(struct mmc_host *host, unsigned long config)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
return gpiod_set_config(ctx->cd_gpio, config);
}
EXPORT_SYMBOL(mmc_gpiod_set_cd_config);
bool mmc_can_gpio_cd(struct mmc_host *host)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
return ctx->cd_gpio ? true : false;
}
EXPORT_SYMBOL(mmc_can_gpio_cd);
/**
* mmc_gpiod_request_ro - request a gpio descriptor for write protection
* @host: mmc host
* @con_id: function within the GPIO consumer
* @idx: index of the GPIO to obtain in the consumer
* @debounce: debounce time in microseconds
*
* Returns zero on success, else an error.
*/
int mmc_gpiod_request_ro(struct mmc_host *host, const char *con_id,
unsigned int idx, unsigned int debounce)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
struct gpio_desc *desc;
int ret;
desc = devm_gpiod_get_index(host->parent, con_id, idx, GPIOD_IN);
if (IS_ERR(desc))
return PTR_ERR(desc);
if (debounce) {
ret = gpiod_set_debounce(desc, debounce);
if (ret < 0)
return ret;
}
if (host->caps2 & MMC_CAP2_RO_ACTIVE_HIGH)
gpiod_toggle_active_low(desc);
ctx->ro_gpio = desc;
return 0;
}
EXPORT_SYMBOL(mmc_gpiod_request_ro);
bool mmc_can_gpio_ro(struct mmc_host *host)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
return ctx->ro_gpio ? true : false;
}
EXPORT_SYMBOL(mmc_can_gpio_ro);