forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
245 lines
6.8 KiB
245 lines
6.8 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* arizona-spi.c -- Arizona SPI bus interface |
|
* |
|
* Copyright 2012 Wolfson Microelectronics plc |
|
* |
|
* Author: Mark Brown <[email protected]> |
|
*/ |
|
|
|
#include <linux/acpi.h> |
|
#include <linux/err.h> |
|
#include <linux/gpio/consumer.h> |
|
#include <linux/gpio/machine.h> |
|
#include <linux/module.h> |
|
#include <linux/pm_runtime.h> |
|
#include <linux/regmap.h> |
|
#include <linux/regulator/consumer.h> |
|
#include <linux/slab.h> |
|
#include <linux/spi/spi.h> |
|
#include <linux/of.h> |
|
#include <uapi/linux/input-event-codes.h> |
|
|
|
#include <linux/mfd/arizona/core.h> |
|
|
|
#include "arizona.h" |
|
|
|
#ifdef CONFIG_ACPI |
|
static const struct acpi_gpio_params reset_gpios = { 1, 0, false }; |
|
static const struct acpi_gpio_params ldoena_gpios = { 2, 0, false }; |
|
|
|
static const struct acpi_gpio_mapping arizona_acpi_gpios[] = { |
|
{ "reset-gpios", &reset_gpios, 1, }, |
|
{ "wlf,ldoena-gpios", &ldoena_gpios, 1 }, |
|
{ } |
|
}; |
|
|
|
/* |
|
* The ACPI resources for the device only describe external GPIO-s. They do |
|
* not provide mappings for the GPIO-s coming from the Arizona codec itself. |
|
*/ |
|
static const struct gpiod_lookup arizona_soc_gpios[] = { |
|
{ "arizona", 2, "wlf,spkvdd-ena", 0, GPIO_ACTIVE_HIGH }, |
|
{ "arizona", 4, "wlf,micd-pol", 0, GPIO_ACTIVE_LOW }, |
|
}; |
|
|
|
/* |
|
* The AOSP 3.5 mm Headset: Accessory Specification gives the following values: |
|
* Function A Play/Pause: 0 ohm |
|
* Function D Voice assistant: 135 ohm |
|
* Function B Volume Up 240 ohm |
|
* Function C Volume Down 470 ohm |
|
* Minimum Mic DC resistance 1000 ohm |
|
* Minimum Ear speaker impedance 16 ohm |
|
* Note the first max value below must be less then the min. speaker impedance, |
|
* to allow CTIA/OMTP detection to work. The other max values are the closest |
|
* value from extcon-arizona.c:arizona_micd_levels halfway 2 button resistances. |
|
*/ |
|
static const struct arizona_micd_range arizona_micd_aosp_ranges[] = { |
|
{ .max = 11, .key = KEY_PLAYPAUSE }, |
|
{ .max = 186, .key = KEY_VOICECOMMAND }, |
|
{ .max = 348, .key = KEY_VOLUMEUP }, |
|
{ .max = 752, .key = KEY_VOLUMEDOWN }, |
|
}; |
|
|
|
static void arizona_spi_acpi_remove_lookup(void *lookup) |
|
{ |
|
gpiod_remove_lookup_table(lookup); |
|
} |
|
|
|
static int arizona_spi_acpi_probe(struct arizona *arizona) |
|
{ |
|
struct gpiod_lookup_table *lookup; |
|
acpi_status status; |
|
int ret; |
|
|
|
/* Add mappings for the 2 ACPI declared GPIOs used for reset and ldo-ena */ |
|
devm_acpi_dev_add_driver_gpios(arizona->dev, arizona_acpi_gpios); |
|
|
|
/* Add lookups for the SoCs own GPIOs used for micdet-polarity and spkVDD-enable */ |
|
lookup = devm_kzalloc(arizona->dev, |
|
struct_size(lookup, table, ARRAY_SIZE(arizona_soc_gpios) + 1), |
|
GFP_KERNEL); |
|
if (!lookup) |
|
return -ENOMEM; |
|
|
|
lookup->dev_id = dev_name(arizona->dev); |
|
memcpy(lookup->table, arizona_soc_gpios, sizeof(arizona_soc_gpios)); |
|
|
|
gpiod_add_lookup_table(lookup); |
|
ret = devm_add_action_or_reset(arizona->dev, arizona_spi_acpi_remove_lookup, lookup); |
|
if (ret) |
|
return ret; |
|
|
|
/* Enable 32KHz clock from SoC to codec for jack-detect */ |
|
status = acpi_evaluate_object(ACPI_HANDLE(arizona->dev), "CLKE", NULL, NULL); |
|
if (ACPI_FAILURE(status)) |
|
dev_warn(arizona->dev, "Failed to enable 32KHz clk ACPI error %d\n", status); |
|
|
|
/* |
|
* Some DSDTs wrongly declare the IRQ trigger-type as IRQF_TRIGGER_FALLING |
|
* The IRQ line will stay low when a new IRQ event happens between reading |
|
* the IRQ status flags and acknowledging them. When the IRQ line stays |
|
* low like this the IRQ will never trigger again when its type is set |
|
* to IRQF_TRIGGER_FALLING. Correct the IRQ trigger-type to fix this. |
|
* |
|
* Note theoretically it is possible that some boards are not capable |
|
* of handling active low level interrupts. In that case setting the |
|
* flag to IRQF_TRIGGER_FALLING would not be a bug (and we would need |
|
* to work around this) but so far all known usages of IRQF_TRIGGER_FALLING |
|
* are a bug in the board's DSDT. |
|
*/ |
|
arizona->pdata.irq_flags = IRQF_TRIGGER_LOW; |
|
|
|
/* Wait 200 ms after jack insertion */ |
|
arizona->pdata.micd_detect_debounce = 200; |
|
|
|
/* Use standard AOSP values for headset-button mappings */ |
|
arizona->pdata.micd_ranges = arizona_micd_aosp_ranges; |
|
arizona->pdata.num_micd_ranges = ARRAY_SIZE(arizona_micd_aosp_ranges); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct acpi_device_id arizona_acpi_match[] = { |
|
{ |
|
.id = "WM510204", |
|
.driver_data = WM5102, |
|
}, |
|
{ |
|
.id = "WM510205", |
|
.driver_data = WM5102, |
|
}, |
|
{ } |
|
}; |
|
MODULE_DEVICE_TABLE(acpi, arizona_acpi_match); |
|
#else |
|
static int arizona_spi_acpi_probe(struct arizona *arizona) |
|
{ |
|
return -ENODEV; |
|
} |
|
#endif |
|
|
|
static int arizona_spi_probe(struct spi_device *spi) |
|
{ |
|
const struct spi_device_id *id = spi_get_device_id(spi); |
|
const void *match_data; |
|
struct arizona *arizona; |
|
const struct regmap_config *regmap_config = NULL; |
|
unsigned long type = 0; |
|
int ret; |
|
|
|
match_data = device_get_match_data(&spi->dev); |
|
if (match_data) |
|
type = (unsigned long)match_data; |
|
else if (id) |
|
type = id->driver_data; |
|
|
|
switch (type) { |
|
case WM5102: |
|
if (IS_ENABLED(CONFIG_MFD_WM5102)) |
|
regmap_config = &wm5102_spi_regmap; |
|
break; |
|
case WM5110: |
|
case WM8280: |
|
if (IS_ENABLED(CONFIG_MFD_WM5110)) |
|
regmap_config = &wm5110_spi_regmap; |
|
break; |
|
case WM1831: |
|
case CS47L24: |
|
if (IS_ENABLED(CONFIG_MFD_CS47L24)) |
|
regmap_config = &cs47l24_spi_regmap; |
|
break; |
|
default: |
|
dev_err(&spi->dev, "Unknown device type %ld\n", type); |
|
return -EINVAL; |
|
} |
|
|
|
if (!regmap_config) { |
|
dev_err(&spi->dev, |
|
"No kernel support for device type %ld\n", type); |
|
return -EINVAL; |
|
} |
|
|
|
arizona = devm_kzalloc(&spi->dev, sizeof(*arizona), GFP_KERNEL); |
|
if (arizona == NULL) |
|
return -ENOMEM; |
|
|
|
arizona->regmap = devm_regmap_init_spi(spi, regmap_config); |
|
if (IS_ERR(arizona->regmap)) { |
|
ret = PTR_ERR(arizona->regmap); |
|
dev_err(&spi->dev, "Failed to allocate register map: %d\n", |
|
ret); |
|
return ret; |
|
} |
|
|
|
arizona->type = type; |
|
arizona->dev = &spi->dev; |
|
arizona->irq = spi->irq; |
|
|
|
if (has_acpi_companion(&spi->dev)) { |
|
ret = arizona_spi_acpi_probe(arizona); |
|
if (ret) |
|
return ret; |
|
} |
|
|
|
return arizona_dev_init(arizona); |
|
} |
|
|
|
static int arizona_spi_remove(struct spi_device *spi) |
|
{ |
|
struct arizona *arizona = spi_get_drvdata(spi); |
|
|
|
arizona_dev_exit(arizona); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct spi_device_id arizona_spi_ids[] = { |
|
{ "wm5102", WM5102 }, |
|
{ "wm5110", WM5110 }, |
|
{ "wm8280", WM8280 }, |
|
{ "wm1831", WM1831 }, |
|
{ "cs47l24", CS47L24 }, |
|
{ }, |
|
}; |
|
MODULE_DEVICE_TABLE(spi, arizona_spi_ids); |
|
|
|
static struct spi_driver arizona_spi_driver = { |
|
.driver = { |
|
.name = "arizona", |
|
.pm = &arizona_pm_ops, |
|
.of_match_table = of_match_ptr(arizona_of_match), |
|
.acpi_match_table = ACPI_PTR(arizona_acpi_match), |
|
}, |
|
.probe = arizona_spi_probe, |
|
.remove = arizona_spi_remove, |
|
.id_table = arizona_spi_ids, |
|
}; |
|
|
|
module_spi_driver(arizona_spi_driver); |
|
|
|
MODULE_SOFTDEP("pre: arizona_ldo1"); |
|
MODULE_DESCRIPTION("Arizona SPI bus interface"); |
|
MODULE_AUTHOR("Mark Brown <[email protected]>"); |
|
MODULE_LICENSE("GPL");
|
|
|