cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

zl38060.c (16117B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2//
      3// Codec driver for Microsemi ZL38060 Connected Home Audio Processor.
      4//
      5// Copyright(c) 2020 Sven Van Asbroeck
      6
      7// The ZL38060 is very flexible and configurable. This driver implements only a
      8// tiny subset of the chip's possible configurations:
      9//
     10// - DSP block bypassed: DAI        routed straight to DACs
     11//                       microphone routed straight to DAI
     12// - chip's internal clock is driven by a 12 MHz external crystal
     13// - chip's DAI connected to CPU is I2S, and bit + frame clock master
     14// - chip must be strapped for "host boot": in this mode, firmware will be
     15//   provided by this driver.
     16
     17#include <linux/gpio/consumer.h>
     18#include <linux/gpio/driver.h>
     19#include <linux/property.h>
     20#include <linux/spi/spi.h>
     21#include <linux/regmap.h>
     22#include <linux/module.h>
     23#include <linux/ihex.h>
     24
     25#include <sound/pcm_params.h>
     26#include <sound/core.h>
     27#include <sound/pcm.h>
     28#include <sound/soc.h>
     29
     30#define DRV_NAME		"zl38060"
     31
     32#define ZL38_RATES		(SNDRV_PCM_RATE_8000  |\
     33				SNDRV_PCM_RATE_16000 |\
     34				SNDRV_PCM_RATE_48000)
     35#define ZL38_FORMATS		SNDRV_PCM_FMTBIT_S16_LE
     36
     37#define HBI_FIRMWARE_PAGE	0xFF
     38#define ZL38_MAX_RAW_XFER	0x100
     39
     40#define REG_TDMA_CFG_CLK	0x0262
     41#define CFG_CLK_PCLK_SHIFT	4
     42#define CFG_CLK_PCLK_MASK	(0x7ff << CFG_CLK_PCLK_SHIFT)
     43#define CFG_CLK_PCLK(bits)	((bits - 1) << CFG_CLK_PCLK_SHIFT)
     44#define CFG_CLK_MASTER		BIT(15)
     45#define CFG_CLK_FSRATE_MASK	0x7
     46#define CFG_CLK_FSRATE_8KHZ	0x1
     47#define CFG_CLK_FSRATE_16KHZ	0x2
     48#define CFG_CLK_FSRATE_48KHZ	0x6
     49
     50#define REG_CLK_CFG		0x0016
     51#define CLK_CFG_SOURCE_XTAL	BIT(15)
     52
     53#define REG_CLK_STATUS		0x0014
     54#define CLK_STATUS_HWRST	BIT(0)
     55
     56#define REG_PARAM_RESULT	0x0034
     57#define PARAM_RESULT_READY	0xD3D3
     58
     59#define REG_PG255_BASE_HI	0x000C
     60#define REG_PG255_OFFS(addr)	((HBI_FIRMWARE_PAGE << 8) | (addr & 0xFF))
     61#define REG_FWR_EXEC		0x012C
     62
     63#define REG_CMD			0x0032
     64#define REG_HW_REV		0x0020
     65#define REG_FW_PROD		0x0022
     66#define REG_FW_REV		0x0024
     67
     68#define REG_SEMA_FLAGS		0x0006
     69#define SEMA_FLAGS_BOOT_CMD	BIT(0)
     70#define SEMA_FLAGS_APP_REBOOT	BIT(1)
     71
     72#define REG_HW_REV		0x0020
     73#define REG_FW_PROD		0x0022
     74#define REG_FW_REV		0x0024
     75#define REG_GPIO_DIR		0x02DC
     76#define REG_GPIO_DAT		0x02DA
     77
     78#define BOOTCMD_LOAD_COMPLETE	0x000D
     79#define BOOTCMD_FW_GO		0x0008
     80
     81#define FIRMWARE_MAJOR		2
     82#define FIRMWARE_MINOR		2
     83
     84struct zl38_codec_priv {
     85	struct device *dev;
     86	struct regmap *regmap;
     87	bool is_stream_in_use[2];
     88	struct gpio_chip *gpio_chip;
     89};
     90
     91static int zl38_fw_issue_command(struct regmap *regmap, u16 cmd)
     92{
     93	unsigned int val;
     94	int err;
     95
     96	err = regmap_read_poll_timeout(regmap, REG_SEMA_FLAGS, val,
     97				       !(val & SEMA_FLAGS_BOOT_CMD), 10000,
     98				       10000 * 100);
     99	if (err)
    100		return err;
    101	err = regmap_write(regmap, REG_CMD, cmd);
    102	if (err)
    103		return err;
    104	err = regmap_update_bits(regmap, REG_SEMA_FLAGS, SEMA_FLAGS_BOOT_CMD,
    105				 SEMA_FLAGS_BOOT_CMD);
    106	if (err)
    107		return err;
    108
    109	return regmap_read_poll_timeout(regmap, REG_CMD, val, !val, 10000,
    110					10000 * 100);
    111}
    112
    113static int zl38_fw_go(struct regmap *regmap)
    114{
    115	int err;
    116
    117	err = zl38_fw_issue_command(regmap, BOOTCMD_LOAD_COMPLETE);
    118	if (err)
    119		return err;
    120
    121	return zl38_fw_issue_command(regmap, BOOTCMD_FW_GO);
    122}
    123
    124static int zl38_fw_enter_boot_mode(struct regmap *regmap)
    125{
    126	unsigned int val;
    127	int err;
    128
    129	err = regmap_update_bits(regmap, REG_CLK_STATUS, CLK_STATUS_HWRST,
    130				 CLK_STATUS_HWRST);
    131	if (err)
    132		return err;
    133
    134	return regmap_read_poll_timeout(regmap, REG_PARAM_RESULT, val,
    135					val == PARAM_RESULT_READY, 1000, 50000);
    136}
    137
    138static int
    139zl38_fw_send_data(struct regmap *regmap, u32 addr, const void *data, u16 len)
    140{
    141	__be32 addr_base = cpu_to_be32(addr & ~0xFF);
    142	int err;
    143
    144	err = regmap_raw_write(regmap, REG_PG255_BASE_HI, &addr_base,
    145			       sizeof(addr_base));
    146	if (err)
    147		return err;
    148	return regmap_raw_write(regmap, REG_PG255_OFFS(addr), data, len);
    149}
    150
    151static int zl38_fw_send_xaddr(struct regmap *regmap, const void *data)
    152{
    153	/* execution address from ihex: 32-bit little endian.
    154	 * device register expects 32-bit big endian.
    155	 */
    156	u32 addr = le32_to_cpup(data);
    157	__be32 baddr = cpu_to_be32(addr);
    158
    159	return regmap_raw_write(regmap, REG_FWR_EXEC, &baddr, sizeof(baddr));
    160}
    161
    162static int zl38_load_firmware(struct device *dev, struct regmap *regmap)
    163{
    164	const struct ihex_binrec *rec;
    165	const struct firmware *fw;
    166	u32 addr;
    167	u16 len;
    168	int err;
    169
    170	/* how to get this firmware:
    171	 * 1. request and download chip firmware from Microsemi
    172	 *    (provided by Microsemi in srec format)
    173	 * 2. convert downloaded firmware from srec to ihex. Simple tool:
    174	 *    https://gitlab.com/TheSven73/s3-to-irec
    175	 * 3. convert ihex to binary (.fw) using ihex2fw tool which is included
    176	 *    with the Linux kernel sources
    177	 */
    178	err = request_ihex_firmware(&fw, "zl38060.fw", dev);
    179	if (err)
    180		return err;
    181	err = zl38_fw_enter_boot_mode(regmap);
    182	if (err)
    183		goto out;
    184	rec = (const struct ihex_binrec *)fw->data;
    185	while (rec) {
    186		addr = be32_to_cpu(rec->addr);
    187		len = be16_to_cpu(rec->len);
    188		if (addr) {
    189			/* regular data ihex record */
    190			err = zl38_fw_send_data(regmap, addr, rec->data, len);
    191		} else if (len == 4) {
    192			/* execution address ihex record */
    193			err = zl38_fw_send_xaddr(regmap, rec->data);
    194		} else {
    195			err = -EINVAL;
    196		}
    197		if (err)
    198			goto out;
    199		/* next ! */
    200		rec = ihex_next_binrec(rec);
    201	}
    202	err = zl38_fw_go(regmap);
    203
    204out:
    205	release_firmware(fw);
    206	return err;
    207}
    208
    209
    210static int zl38_software_reset(struct regmap *regmap)
    211{
    212	unsigned int val;
    213	int err;
    214
    215	err = regmap_update_bits(regmap, REG_SEMA_FLAGS, SEMA_FLAGS_APP_REBOOT,
    216				 SEMA_FLAGS_APP_REBOOT);
    217	if (err)
    218		return err;
    219
    220	/* wait for host bus interface to settle.
    221	 * Not sure if this is required: Microsemi's vendor driver does this,
    222	 * but the firmware manual does not mention it. Leave it in, there's
    223	 * little downside, apart from a slower reset.
    224	 */
    225	msleep(50);
    226
    227	return regmap_read_poll_timeout(regmap, REG_SEMA_FLAGS, val,
    228					!(val & SEMA_FLAGS_APP_REBOOT), 10000,
    229					10000 * 100);
    230}
    231
    232static int zl38_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
    233{
    234	struct zl38_codec_priv *priv = snd_soc_dai_get_drvdata(dai);
    235	int err;
    236
    237	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
    238	case SND_SOC_DAIFMT_I2S:
    239		/* firmware default is normal i2s */
    240		break;
    241	default:
    242		return -EINVAL;
    243	}
    244
    245	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
    246	case SND_SOC_DAIFMT_NB_NF:
    247		/* firmware default is normal bitclock and frame */
    248		break;
    249	default:
    250		return -EINVAL;
    251	}
    252
    253	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
    254	case SND_SOC_DAIFMT_CBP_CFP:
    255		/* always 32 bits per frame (= 16 bits/channel, 2 channels) */
    256		err = regmap_update_bits(priv->regmap, REG_TDMA_CFG_CLK,
    257					 CFG_CLK_MASTER | CFG_CLK_PCLK_MASK,
    258					 CFG_CLK_MASTER | CFG_CLK_PCLK(32));
    259		if (err)
    260			return err;
    261		break;
    262	default:
    263		return -EINVAL;
    264	}
    265
    266	return 0;
    267}
    268
    269static int zl38_hw_params(struct snd_pcm_substream *substream,
    270			  struct snd_pcm_hw_params *params,
    271			  struct snd_soc_dai *dai)
    272{
    273	struct zl38_codec_priv *priv = snd_soc_dai_get_drvdata(dai);
    274	bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
    275	unsigned int fsrate;
    276	int err;
    277
    278	/* We cannot change hw_params while the dai is already in use - the
    279	 * software reset will corrupt the audio. However, this is not required,
    280	 * as the chip's TDM buses are fully symmetric, which mandates identical
    281	 * rates, channels, and samplebits for record and playback.
    282	 */
    283	if (priv->is_stream_in_use[!tx])
    284		goto skip_setup;
    285
    286	switch (params_rate(params)) {
    287	case 8000:
    288		fsrate = CFG_CLK_FSRATE_8KHZ;
    289		break;
    290	case 16000:
    291		fsrate = CFG_CLK_FSRATE_16KHZ;
    292		break;
    293	case 48000:
    294		fsrate = CFG_CLK_FSRATE_48KHZ;
    295		break;
    296	default:
    297		return -EINVAL;
    298	}
    299
    300	err = regmap_update_bits(priv->regmap, REG_TDMA_CFG_CLK,
    301				 CFG_CLK_FSRATE_MASK, fsrate);
    302	if (err)
    303		return err;
    304
    305	/* chip requires a software reset to apply audio register changes */
    306	err = zl38_software_reset(priv->regmap);
    307	if (err)
    308		return err;
    309
    310skip_setup:
    311	priv->is_stream_in_use[tx] = true;
    312
    313	return 0;
    314}
    315
    316static int zl38_hw_free(struct snd_pcm_substream *substream,
    317			struct snd_soc_dai *dai)
    318{
    319	struct zl38_codec_priv *priv = snd_soc_dai_get_drvdata(dai);
    320	bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
    321
    322	priv->is_stream_in_use[tx] = false;
    323
    324	return 0;
    325}
    326
    327/* stereo bypass with no AEC */
    328static const struct reg_sequence cp_config_stereo_bypass[] = {
    329	/* interconnects must be programmed first */
    330	{ 0x0210, 0x0005 },	/* DAC1   in <= I2S1-L */
    331	{ 0x0212, 0x0006 },	/* DAC2   in <= I2S1-R */
    332	{ 0x0214, 0x0001 },	/* I2S1-L in <= MIC1   */
    333	{ 0x0216, 0x0001 },	/* I2S1-R in <= MIC1   */
    334	{ 0x0224, 0x0000 },	/* AEC-S  in <= n/a    */
    335	{ 0x0226, 0x0000 },	/* AEC-R  in <= n/a    */
    336	/* output enables must be programmed next */
    337	{ 0x0202, 0x000F },	/* enable I2S1 + DAC   */
    338};
    339
    340static const struct snd_soc_dai_ops zl38_dai_ops = {
    341	.set_fmt = zl38_set_fmt,
    342	.hw_params = zl38_hw_params,
    343	.hw_free = zl38_hw_free,
    344};
    345
    346static struct snd_soc_dai_driver zl38_dai = {
    347	.name = "zl38060-tdma",
    348	.playback = {
    349		.stream_name = "Playback",
    350		.channels_min = 2,
    351		.channels_max = 2,
    352		.rates = ZL38_RATES,
    353		.formats = ZL38_FORMATS,
    354	},
    355	.capture = {
    356		.stream_name = "Capture",
    357		.channels_min = 2,
    358		.channels_max = 2,
    359		.rates = ZL38_RATES,
    360		.formats = ZL38_FORMATS,
    361	},
    362	.ops = &zl38_dai_ops,
    363	.symmetric_rate = 1,
    364	.symmetric_sample_bits = 1,
    365	.symmetric_channels = 1,
    366};
    367
    368static const struct snd_soc_dapm_widget zl38_dapm_widgets[] = {
    369	SND_SOC_DAPM_OUTPUT("DAC1"),
    370	SND_SOC_DAPM_OUTPUT("DAC2"),
    371
    372	SND_SOC_DAPM_INPUT("DMICL"),
    373};
    374
    375static const struct snd_soc_dapm_route zl38_dapm_routes[] = {
    376	{ "DAC1",  NULL, "Playback" },
    377	{ "DAC2",  NULL, "Playback" },
    378
    379	{ "Capture",  NULL, "DMICL" },
    380};
    381
    382static const struct snd_soc_component_driver zl38_component_dev = {
    383	.dapm_widgets		= zl38_dapm_widgets,
    384	.num_dapm_widgets	= ARRAY_SIZE(zl38_dapm_widgets),
    385	.dapm_routes		= zl38_dapm_routes,
    386	.num_dapm_routes	= ARRAY_SIZE(zl38_dapm_routes),
    387	.endianness		= 1,
    388	.non_legacy_dai_naming	= 1,
    389};
    390
    391static void chip_gpio_set(struct gpio_chip *c, unsigned int offset, int val)
    392{
    393	struct regmap *regmap = gpiochip_get_data(c);
    394	unsigned int mask = BIT(offset);
    395
    396	regmap_update_bits(regmap, REG_GPIO_DAT, mask, val ? mask : 0);
    397}
    398
    399static int chip_gpio_get(struct gpio_chip *c, unsigned int offset)
    400{
    401	struct regmap *regmap = gpiochip_get_data(c);
    402	unsigned int mask = BIT(offset);
    403	unsigned int val;
    404	int err;
    405
    406	err = regmap_read(regmap, REG_GPIO_DAT, &val);
    407	if (err)
    408		return err;
    409
    410	return !!(val & mask);
    411}
    412
    413static int chip_direction_input(struct gpio_chip *c, unsigned int offset)
    414{
    415	struct regmap *regmap = gpiochip_get_data(c);
    416	unsigned int mask = BIT(offset);
    417
    418	return regmap_update_bits(regmap, REG_GPIO_DIR, mask, 0);
    419}
    420
    421static int
    422chip_direction_output(struct gpio_chip *c, unsigned int offset, int val)
    423{
    424	struct regmap *regmap = gpiochip_get_data(c);
    425	unsigned int mask = BIT(offset);
    426
    427	chip_gpio_set(c, offset, val);
    428	return regmap_update_bits(regmap, REG_GPIO_DIR, mask, mask);
    429}
    430
    431static const struct gpio_chip template_chip = {
    432	.owner = THIS_MODULE,
    433	.label = DRV_NAME,
    434
    435	.base = -1,
    436	.ngpio = 14,
    437	.direction_input = chip_direction_input,
    438	.direction_output = chip_direction_output,
    439	.get = chip_gpio_get,
    440	.set = chip_gpio_set,
    441
    442	.can_sleep = true,
    443};
    444
    445static int zl38_check_revision(struct device *dev, struct regmap *regmap)
    446{
    447	unsigned int hwrev, fwprod, fwrev;
    448	int fw_major, fw_minor, fw_micro;
    449	int err;
    450
    451	err = regmap_read(regmap, REG_HW_REV, &hwrev);
    452	if (err)
    453		return err;
    454	err = regmap_read(regmap, REG_FW_PROD, &fwprod);
    455	if (err)
    456		return err;
    457	err = regmap_read(regmap, REG_FW_REV, &fwrev);
    458	if (err)
    459		return err;
    460
    461	fw_major = (fwrev >> 12) & 0xF;
    462	fw_minor = (fwrev >>  8) & 0xF;
    463	fw_micro = fwrev & 0xFF;
    464	dev_info(dev, "hw rev 0x%x, fw product code %d, firmware rev %d.%d.%d",
    465		 hwrev & 0x1F, fwprod, fw_major, fw_minor, fw_micro);
    466
    467	if (fw_major != FIRMWARE_MAJOR || fw_minor < FIRMWARE_MINOR) {
    468		dev_err(dev, "unsupported firmware. driver supports %d.%d",
    469			FIRMWARE_MAJOR, FIRMWARE_MINOR);
    470		return -EINVAL;
    471	}
    472
    473	return 0;
    474}
    475
    476static int zl38_bus_read(void *context,
    477			 const void *reg_buf, size_t reg_size,
    478			 void *val_buf, size_t val_size)
    479{
    480	struct spi_device *spi = context;
    481	const u8 *reg_buf8 = reg_buf;
    482	size_t len = 0;
    483	u8 offs, page;
    484	u8 txbuf[4];
    485
    486	if (reg_size != 2 || val_size > ZL38_MAX_RAW_XFER)
    487		return -EINVAL;
    488
    489	offs = reg_buf8[1] >> 1;
    490	page = reg_buf8[0];
    491
    492	if (page) {
    493		txbuf[len++] = 0xFE;
    494		txbuf[len++] = page == HBI_FIRMWARE_PAGE ? 0xFF : page - 1;
    495		txbuf[len++] = offs;
    496		txbuf[len++] = val_size / 2 - 1;
    497	} else {
    498		txbuf[len++] = offs | 0x80;
    499		txbuf[len++] = val_size / 2 - 1;
    500	}
    501
    502	return spi_write_then_read(spi, txbuf, len, val_buf, val_size);
    503}
    504
    505static int zl38_bus_write(void *context, const void *data, size_t count)
    506{
    507	struct spi_device *spi = context;
    508	u8 buf[4 + ZL38_MAX_RAW_XFER];
    509	size_t val_len, len = 0;
    510	const u8 *data8 = data;
    511	u8 offs, page;
    512
    513	if (count > (2 + ZL38_MAX_RAW_XFER) || count < 4)
    514		return -EINVAL;
    515	val_len = count - 2;
    516	offs = data8[1] >> 1;
    517	page = data8[0];
    518
    519	if (page) {
    520		buf[len++] = 0xFE;
    521		buf[len++] = page == HBI_FIRMWARE_PAGE ? 0xFF : page - 1;
    522		buf[len++] = offs;
    523		buf[len++] = (val_len / 2 - 1) | 0x80;
    524	} else {
    525		buf[len++] = offs | 0x80;
    526		buf[len++] = (val_len / 2 - 1) | 0x80;
    527	}
    528	memcpy(buf + len, data8 + 2, val_len);
    529	len += val_len;
    530
    531	return spi_write(spi, buf, len);
    532}
    533
    534static const struct regmap_bus zl38_regmap_bus = {
    535	.read = zl38_bus_read,
    536	.write = zl38_bus_write,
    537	.max_raw_write = ZL38_MAX_RAW_XFER,
    538	.max_raw_read = ZL38_MAX_RAW_XFER,
    539};
    540
    541static const struct regmap_config zl38_regmap_conf = {
    542	.reg_bits = 16,
    543	.val_bits = 16,
    544	.reg_stride = 2,
    545	.use_single_read = true,
    546	.use_single_write = true,
    547};
    548
    549static int zl38_spi_probe(struct spi_device *spi)
    550{
    551	struct device *dev = &spi->dev;
    552	struct zl38_codec_priv *priv;
    553	struct gpio_desc *reset_gpio;
    554	int err;
    555
    556	/* get the chip to a known state by putting it in reset */
    557	reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
    558	if (IS_ERR(reset_gpio))
    559		return PTR_ERR(reset_gpio);
    560	if (reset_gpio) {
    561		/* datasheet: need > 10us for a digital + analog reset */
    562		usleep_range(15, 50);
    563		/* take the chip out of reset */
    564		gpiod_set_value_cansleep(reset_gpio, 0);
    565		/* datasheet: need > 3ms for digital section to become stable */
    566		usleep_range(3000, 10000);
    567	}
    568
    569	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    570	if (!priv)
    571		return -ENOMEM;
    572
    573	priv->dev = dev;
    574	dev_set_drvdata(dev, priv);
    575	priv->regmap = devm_regmap_init(dev, &zl38_regmap_bus, spi,
    576					&zl38_regmap_conf);
    577	if (IS_ERR(priv->regmap))
    578		return PTR_ERR(priv->regmap);
    579
    580	err = zl38_load_firmware(dev, priv->regmap);
    581	if (err)
    582		return err;
    583
    584	err = zl38_check_revision(dev, priv->regmap);
    585	if (err)
    586		return err;
    587
    588	priv->gpio_chip = devm_kmemdup(dev, &template_chip,
    589				       sizeof(template_chip), GFP_KERNEL);
    590	if (!priv->gpio_chip)
    591		return -ENOMEM;
    592	priv->gpio_chip->parent = dev;
    593	err = devm_gpiochip_add_data(dev, priv->gpio_chip, priv->regmap);
    594	if (err)
    595		return err;
    596
    597	/* setup the cross-point switch for stereo bypass */
    598	err = regmap_multi_reg_write(priv->regmap, cp_config_stereo_bypass,
    599				     ARRAY_SIZE(cp_config_stereo_bypass));
    600	if (err)
    601		return err;
    602	/* setup for 12MHz crystal connected to the chip */
    603	err = regmap_update_bits(priv->regmap, REG_CLK_CFG, CLK_CFG_SOURCE_XTAL,
    604				 CLK_CFG_SOURCE_XTAL);
    605	if (err)
    606		return err;
    607
    608	return devm_snd_soc_register_component(dev, &zl38_component_dev,
    609					       &zl38_dai, 1);
    610}
    611
    612static const struct of_device_id zl38_dt_ids[] = {
    613	{ .compatible = "mscc,zl38060", },
    614	{ /* sentinel */ }
    615};
    616MODULE_DEVICE_TABLE(of, zl38_dt_ids);
    617
    618static const struct spi_device_id zl38_spi_ids[] = {
    619	{ "zl38060", 0 },
    620	{ /* sentinel */ }
    621};
    622MODULE_DEVICE_TABLE(spi, zl38_spi_ids);
    623
    624static struct spi_driver zl38060_spi_driver = {
    625	.driver	= {
    626		.name = DRV_NAME,
    627		.of_match_table = of_match_ptr(zl38_dt_ids),
    628	},
    629	.probe = zl38_spi_probe,
    630	.id_table = zl38_spi_ids,
    631};
    632module_spi_driver(zl38060_spi_driver);
    633
    634MODULE_DESCRIPTION("ASoC ZL38060 driver");
    635MODULE_AUTHOR("Sven Van Asbroeck <TheSven73@gmail.com>");
    636MODULE_LICENSE("GPL v2");