Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9bf5c3d1 authored by Robert Jarzmik's avatar Robert Jarzmik Committed by Mark Brown
Browse files

ASoC: ac97: add gpio chip



The AC97 specification provides a guide for 16 GPIOs in the codecs. If
the gpiolib is compiled in the kernel, declare a gpio chip.

This was tested with a pxa27x board (mioa701) and a wm9713 codec.

Signed-off-by: default avatarRobert Jarzmik <robert.jarzmik@free.fr>
Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 8005c49d
Loading
Loading
Loading
Loading
+3 −0
Original line number Original line Diff line number Diff line
@@ -417,11 +417,13 @@
#define AC97_RATES_MIC_ADC	4
#define AC97_RATES_MIC_ADC	4
#define AC97_RATES_SPDIF	5
#define AC97_RATES_SPDIF	5


#define AC97_NUM_GPIOS		16
/*
/*
 *
 *
 */
 */


struct snd_ac97;
struct snd_ac97;
struct snd_ac97_gpio_priv;
struct snd_pcm_chmap;
struct snd_pcm_chmap;


struct snd_ac97_build_ops {
struct snd_ac97_build_ops {
@@ -529,6 +531,7 @@ struct snd_ac97 {
	struct delayed_work power_work;
	struct delayed_work power_work;
#endif
#endif
	struct device dev;
	struct device dev;
	struct snd_ac97_gpio_priv *gpio_priv;


	struct snd_pcm_chmap *chmaps[2]; /* channel-maps (optional) */
	struct snd_pcm_chmap *chmaps[2]; /* channel-maps (optional) */
};
};
+125 −0
Original line number Original line Diff line number Diff line
@@ -20,6 +20,7 @@
#include <linux/delay.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/export.h>
#include <linux/gpio.h>
#include <linux/gpio.h>
#include <linux/gpio/driver.h>
#include <linux/init.h>
#include <linux/init.h>
#include <linux/of_gpio.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/of.h>
@@ -38,6 +39,14 @@ struct snd_ac97_reset_cfg {
	int gpio_reset;
	int gpio_reset;
};
};


struct snd_ac97_gpio_priv {
#ifdef CONFIG_GPIOLIB
	struct gpio_chip gpio_chip;
#endif
	unsigned int gpios_set;
	struct snd_soc_codec *codec;
};

static struct snd_ac97_bus soc_ac97_bus = {
static struct snd_ac97_bus soc_ac97_bus = {
	.ops = NULL, /* Gets initialized in snd_soc_set_ac97_ops() */
	.ops = NULL, /* Gets initialized in snd_soc_set_ac97_ops() */
};
};
@@ -47,6 +56,117 @@ static void soc_ac97_device_release(struct device *dev)
	kfree(to_ac97_t(dev));
	kfree(to_ac97_t(dev));
}
}


#ifdef CONFIG_GPIOLIB
static inline struct snd_soc_codec *gpio_to_codec(struct gpio_chip *chip)
{
	struct snd_ac97_gpio_priv *gpio_priv =
		container_of(chip, struct snd_ac97_gpio_priv, gpio_chip);

	return gpio_priv->codec;
}

static int snd_soc_ac97_gpio_request(struct gpio_chip *chip, unsigned offset)
{
	if (offset >= AC97_NUM_GPIOS)
		return -EINVAL;

	return 0;
}

static int snd_soc_ac97_gpio_direction_in(struct gpio_chip *chip,
					  unsigned offset)
{
	struct snd_soc_codec *codec = gpio_to_codec(chip);

	dev_dbg(codec->dev, "set gpio %d to output\n", offset);
	return snd_soc_update_bits(codec, AC97_GPIO_CFG,
				   1 << offset, 1 << offset);
}

static int snd_soc_ac97_gpio_get(struct gpio_chip *chip, unsigned offset)
{
	struct snd_soc_codec *codec = gpio_to_codec(chip);
	int ret;

	ret = snd_soc_read(codec, AC97_GPIO_STATUS);
	dev_dbg(codec->dev, "get gpio %d : %d\n", offset,
		ret < 0 ? ret : ret & (1 << offset));

	return ret < 0 ? ret : ret & (1 << offset);
}

static void snd_soc_ac97_gpio_set(struct gpio_chip *chip, unsigned offset,
				  int value)
{
	struct snd_ac97_gpio_priv *gpio_priv =
		container_of(chip, struct snd_ac97_gpio_priv, gpio_chip);
	struct snd_soc_codec *codec = gpio_to_codec(chip);

	gpio_priv->gpios_set &= ~(1 << offset);
	gpio_priv->gpios_set |= (!!value) << offset;
	snd_soc_write(codec, AC97_GPIO_STATUS, gpio_priv->gpios_set);
	dev_dbg(codec->dev, "set gpio %d to %d\n", offset, !!value);
}

static int snd_soc_ac97_gpio_direction_out(struct gpio_chip *chip,
				     unsigned offset, int value)
{
	struct snd_soc_codec *codec = gpio_to_codec(chip);

	dev_dbg(codec->dev, "set gpio %d to output\n", offset);
	snd_soc_ac97_gpio_set(chip, offset, value);
	return snd_soc_update_bits(codec, AC97_GPIO_CFG, 1 << offset, 0);
}

static struct gpio_chip snd_soc_ac97_gpio_chip = {
	.label			= "snd_soc_ac97",
	.owner			= THIS_MODULE,
	.request		= snd_soc_ac97_gpio_request,
	.direction_input	= snd_soc_ac97_gpio_direction_in,
	.get			= snd_soc_ac97_gpio_get,
	.direction_output	= snd_soc_ac97_gpio_direction_out,
	.set			= snd_soc_ac97_gpio_set,
	.can_sleep		= 1,
};

static int snd_soc_ac97_init_gpio(struct snd_ac97 *ac97,
				  struct snd_soc_codec *codec)
{
	struct snd_ac97_gpio_priv *gpio_priv;
	int ret;

	gpio_priv = devm_kzalloc(codec->dev, sizeof(*gpio_priv), GFP_KERNEL);
	if (!gpio_priv)
		return -ENOMEM;
	ac97->gpio_priv = gpio_priv;
	gpio_priv->codec = codec;
	gpio_priv->gpio_chip = snd_soc_ac97_gpio_chip;
	gpio_priv->gpio_chip.ngpio = AC97_NUM_GPIOS;
	gpio_priv->gpio_chip.dev = codec->dev;
	gpio_priv->gpio_chip.base = -1;

	ret = gpiochip_add(&gpio_priv->gpio_chip);
	if (ret != 0)
		dev_err(codec->dev, "Failed to add GPIOs: %d\n", ret);
	return ret;
}

static void snd_soc_ac97_free_gpio(struct snd_ac97 *ac97)
{
	gpiochip_remove(&ac97->gpio_priv->gpio_chip);
}
#else
static int snd_soc_ac97_init_gpio(struct snd_ac97 *ac97,
				  struct snd_soc_codec *codec)
{
	return 0;
}

static void snd_soc_ac97_free_gpio(struct snd_ac97 *ac97)
{
}
#endif

/**
/**
 * snd_soc_alloc_ac97_codec() - Allocate new a AC'97 device
 * snd_soc_alloc_ac97_codec() - Allocate new a AC'97 device
 * @codec: The CODEC for which to create the AC'97 device
 * @codec: The CODEC for which to create the AC'97 device
@@ -119,6 +239,10 @@ struct snd_ac97 *snd_soc_new_ac97_codec(struct snd_soc_codec *codec,
	if (ret)
	if (ret)
		goto err_put_device;
		goto err_put_device;


	ret = snd_soc_ac97_init_gpio(ac97, codec);
	if (ret)
		goto err_put_device;

	return ac97;
	return ac97;


err_put_device:
err_put_device:
@@ -135,6 +259,7 @@ EXPORT_SYMBOL_GPL(snd_soc_new_ac97_codec);
 */
 */
void snd_soc_free_ac97_codec(struct snd_ac97 *ac97)
void snd_soc_free_ac97_codec(struct snd_ac97 *ac97)
{
{
	snd_soc_ac97_free_gpio(ac97);
	device_del(&ac97->dev);
	device_del(&ac97->dev);
	ac97->bus = NULL;
	ac97->bus = NULL;
	put_device(&ac97->dev);
	put_device(&ac97->dev);