summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/sound/wm8903.h23
-rw-r--r--sound/soc/codecs/wm8903.c129
2 files changed, 151 insertions, 1 deletions
diff --git a/include/sound/wm8903.h b/include/sound/wm8903.h
index b4a0db2307ef..3c993bd23f2c 100644
--- a/include/sound/wm8903.h
+++ b/include/sound/wm8903.h
@@ -15,6 +15,22 @@
#define WM8903_GPIO_NO_CONFIG 0x8000
/*
+ * WM8903_GPn_FN values
+ *
+ * See datasheets for list of valid values per pin
+ */
+#define WM8903_GPn_FN_GPIO_OUTPUT 0
+#define WM8903_GPn_FN_GPIO_BCLK 1
+#define WM8903_GPn_FN_GPIO_IRQ_OUTPUT 1
+#define WM8903_GPn_FN_GPIO_INPUT 3
+#define WM8903_GPn_FN_GPIO_MICBIAS_CURRENT_DETECT 4
+#define WM8903_GPn_FN_GPIO_MICBIAS_SHORT_DETECT 5
+#define WM8903_GPn_FN_GPIO_DMIC_LR_CLK_OUTPUT 6
+#define WM8903_GPn_FN_GPIO_RESERVED 7
+#define WM8903_GPn_FN_GPIO_FLL_LOCK_OUTPUT 8
+#define WM8903_GPn_FN_GPIO_FFL_CLOCK_OUTPUT 9
+
+/*
* R6 (0x06) - Mic Bias Control 0
*/
#define WM8903_MICDET_HYST_ENA 0x0080 /* MICDET_HYST_ENA */
@@ -231,6 +247,9 @@
#define WM8903_GP5_DB_SHIFT 0 /* GP5_DB */
#define WM8903_GP5_DB_WIDTH 1 /* GP5_DB */
+/* the number of gpio pins */
+#define WM8903_NUM_GPIO 5
+
struct wm8903_platform_data {
bool irq_active_low; /* Set if IRQ active low, default high */
@@ -243,7 +262,9 @@ struct wm8903_platform_data {
int micdet_delay; /* Delay after microphone detection (ms) */
- u32 gpio_cfg[5]; /* Default register values for GPIO pin mux */
+ int gpio_base;
+
+ u32 gpio_cfg[WM8903_NUM_GPIO]; /* Default register values for GPIO pin mux */
};
#endif
diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c
index 8a254ad332d7..eee90e43c231 100644
--- a/sound/soc/codecs/wm8903.c
+++ b/sound/soc/codecs/wm8903.c
@@ -23,6 +23,7 @@
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
+#include <linux/gpio.h>
#include <sound/core.h>
#include <sound/jack.h>
#include <sound/pcm.h>
@@ -374,8 +375,132 @@ struct wm8903_priv {
struct snd_pcm_substream *master_substream;
struct snd_pcm_substream *slave_substream;
+
+#ifdef CONFIG_GPIOLIB
+ struct gpio_chip gpio_chip;
+#endif
};
+#ifdef CONFIG_GPIOLIB
+static inline struct wm8903_priv *gpio_to_wm8903(struct gpio_chip* chip)
+{
+ return container_of(chip, struct wm8903_priv, gpio_chip);
+}
+
+static int wm8903_gpio_request(struct gpio_chip* chip,
+ unsigned int offset)
+{
+ if (offset >= WM8903_NUM_GPIO)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int wm8903_gpio_direction_in(struct gpio_chip* chip,
+ unsigned int offset)
+{
+ struct wm8903_priv* wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec* codec = &(wm8903->codec);
+ unsigned int mask, val;
+
+ mask = WM8903_GP1_FN_MASK | WM8903_GP1_DIR_MASK;
+ val = (WM8903_GPn_FN_GPIO_INPUT << WM8903_GP1_FN_SHIFT) |
+ WM8903_GP1_DIR;
+
+ return snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset,
+ mask, val);
+}
+
+static int wm8903_gpio_direction_out(struct gpio_chip* chip,
+ unsigned int offset, int value)
+{
+ struct wm8903_priv* wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec* codec = &(wm8903->codec);
+ unsigned int mask, val;
+
+ mask = WM8903_GP1_FN_MASK | WM8903_GP1_DIR_MASK | WM8903_GP1_LVL_MASK;
+ val = (WM8903_GPn_FN_GPIO_OUTPUT << WM8903_GP1_FN_SHIFT) |
+ (!!value << WM8903_GP1_LVL_SHIFT);
+
+ return snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset,
+ mask, val);
+}
+
+static int wm8903_gpio_get(struct gpio_chip* chip, unsigned int offset)
+{
+ struct wm8903_priv* wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec* codec = &(wm8903->codec);
+ int reg;
+
+ reg = snd_soc_read(codec, WM8903_GPIO_CONTROL_1 + offset);
+
+ return (reg & WM8903_GP1_LVL_MASK) >> WM8903_GP1_LVL_SHIFT;
+}
+
+static void wm8903_gpio_set(struct gpio_chip* chip, unsigned int offset,
+ int value)
+{
+ struct wm8903_priv* wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec* codec = &(wm8903->codec);
+
+ snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset,
+ WM8903_GP1_LVL_MASK,
+ !!value << WM8903_GP1_LVL_SHIFT);
+}
+
+static struct gpio_chip wm8903_gpio_chip = {
+ .label = "wm8903",
+ .owner = THIS_MODULE,
+ .request = wm8903_gpio_request,
+ .direction_input = wm8903_gpio_direction_in,
+ .direction_output = wm8903_gpio_direction_out,
+ .get = wm8903_gpio_get,
+ .set = wm8903_gpio_set,
+ .can_sleep = 1,
+};
+
+static void wm8903_init_gpio(struct snd_soc_codec* codec)
+{
+ struct wm8903_priv* wm8903 = snd_soc_codec_get_drvdata(codec);
+ struct wm8903_platform_data* pdata = dev_get_platdata(codec->dev);
+ int ret;
+
+ wm8903->gpio_chip = wm8903_gpio_chip;
+ wm8903->gpio_chip.ngpio = WM8903_NUM_GPIO;
+ wm8903->gpio_chip.dev = codec->dev;
+
+ if (pdata && pdata->gpio_base)
+ wm8903->gpio_chip.base = pdata->gpio_base;
+ else
+ wm8903->gpio_chip.base = -1;
+
+ ret = gpiochip_add(&wm8903->gpio_chip);
+ if (ret != 0)
+ dev_err(codec->dev,
+ "Failed to add GPIOs for wm8903: %d\n", ret);
+}
+
+static void wm8903_free_gpio(struct snd_soc_codec* codec)
+{
+ struct wm8903_priv* wm8903 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ ret = gpiochip_remove(&wm8903->gpio_chip);
+ if (ret != 0)
+ dev_err(codec->dev,
+ "Failed to remove GPIOs for wm8903: %d\n", ret);
+}
+#else
+static void wm8903_init_gpio(struct snd_soc_codec* codec)
+{
+}
+
+static void wm8903_free_gpio(struct snd_soc_codec* codec)
+{
+}
+#endif
+
+
static int wm8903_volatile_register(unsigned int reg)
{
switch (reg) {
@@ -2019,6 +2144,8 @@ static int wm8903_probe(struct platform_device *pdev)
ARRAY_SIZE(wm8903_snd_controls));
wm8903_add_widgets(socdev->card->codec);
+ wm8903_init_gpio(socdev->card->codec);
+
return ret;
err:
@@ -2037,6 +2164,8 @@ static int wm8903_remove(struct platform_device *pdev)
snd_soc_free_pcms(socdev);
snd_soc_dapm_free(socdev);
+ wm8903_free_gpio(codec);
+
return 0;
}