18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * rt5677-spi.c -- RT5677 ALSA SoC audio codec driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2013 Realtek Semiconductor Corp. 68c2ecf20Sopenharmony_ci * Author: Oder Chiou <oder_chiou@realtek.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/input.h> 118c2ecf20Sopenharmony_ci#include <linux/spi/spi.h> 128c2ecf20Sopenharmony_ci#include <linux/device.h> 138c2ecf20Sopenharmony_ci#include <linux/init.h> 148c2ecf20Sopenharmony_ci#include <linux/delay.h> 158c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 168c2ecf20Sopenharmony_ci#include <linux/irq.h> 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci#include <linux/sched.h> 198c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 208c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h> 218c2ecf20Sopenharmony_ci#include <linux/pm_qos.h> 228c2ecf20Sopenharmony_ci#include <linux/sysfs.h> 238c2ecf20Sopenharmony_ci#include <linux/clk.h> 248c2ecf20Sopenharmony_ci#include <linux/firmware.h> 258c2ecf20Sopenharmony_ci#include <linux/acpi.h> 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#include <sound/soc.h> 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#include "rt5677.h" 308c2ecf20Sopenharmony_ci#include "rt5677-spi.h" 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define DRV_NAME "rt5677spi" 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci#define RT5677_SPI_BURST_LEN 240 358c2ecf20Sopenharmony_ci#define RT5677_SPI_HEADER 5 368c2ecf20Sopenharmony_ci#define RT5677_SPI_FREQ 6000000 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci/* The AddressPhase and DataPhase of SPI commands are MSB first on the wire. 398c2ecf20Sopenharmony_ci * DataPhase word size of 16-bit commands is 2 bytes. 408c2ecf20Sopenharmony_ci * DataPhase word size of 32-bit commands is 4 bytes. 418c2ecf20Sopenharmony_ci * DataPhase word size of burst commands is 8 bytes. 428c2ecf20Sopenharmony_ci * The DSP CPU is little-endian. 438c2ecf20Sopenharmony_ci */ 448c2ecf20Sopenharmony_ci#define RT5677_SPI_WRITE_BURST 0x5 458c2ecf20Sopenharmony_ci#define RT5677_SPI_READ_BURST 0x4 468c2ecf20Sopenharmony_ci#define RT5677_SPI_WRITE_32 0x3 478c2ecf20Sopenharmony_ci#define RT5677_SPI_READ_32 0x2 488c2ecf20Sopenharmony_ci#define RT5677_SPI_WRITE_16 0x1 498c2ecf20Sopenharmony_ci#define RT5677_SPI_READ_16 0x0 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci#define RT5677_BUF_BYTES_TOTAL 0x20000 528c2ecf20Sopenharmony_ci#define RT5677_MIC_BUF_ADDR 0x60030000 538c2ecf20Sopenharmony_ci#define RT5677_MODEL_ADDR 0x5FFC9800 548c2ecf20Sopenharmony_ci#define RT5677_MIC_BUF_BYTES ((u32)(RT5677_BUF_BYTES_TOTAL - \ 558c2ecf20Sopenharmony_ci sizeof(u32))) 568c2ecf20Sopenharmony_ci#define RT5677_MIC_BUF_FIRST_READ_SIZE 0x10000 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic struct spi_device *g_spi; 598c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(spi_mutex); 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistruct rt5677_dsp { 628c2ecf20Sopenharmony_ci struct device *dev; 638c2ecf20Sopenharmony_ci struct delayed_work copy_work; 648c2ecf20Sopenharmony_ci struct mutex dma_lock; 658c2ecf20Sopenharmony_ci struct snd_pcm_substream *substream; 668c2ecf20Sopenharmony_ci size_t dma_offset; /* zero-based offset into runtime->dma_area */ 678c2ecf20Sopenharmony_ci size_t avail_bytes; /* number of new bytes since last period */ 688c2ecf20Sopenharmony_ci u32 mic_read_offset; /* zero-based offset into DSP's mic buffer */ 698c2ecf20Sopenharmony_ci bool new_hotword; /* a new hotword is fired */ 708c2ecf20Sopenharmony_ci}; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_cistatic const struct snd_pcm_hardware rt5677_spi_pcm_hardware = { 738c2ecf20Sopenharmony_ci .info = SNDRV_PCM_INFO_MMAP | 748c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_MMAP_VALID | 758c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_INTERLEAVED, 768c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, 778c2ecf20Sopenharmony_ci .period_bytes_min = PAGE_SIZE, 788c2ecf20Sopenharmony_ci .period_bytes_max = RT5677_BUF_BYTES_TOTAL / 8, 798c2ecf20Sopenharmony_ci .periods_min = 8, 808c2ecf20Sopenharmony_ci .periods_max = 8, 818c2ecf20Sopenharmony_ci .channels_min = 1, 828c2ecf20Sopenharmony_ci .channels_max = 1, 838c2ecf20Sopenharmony_ci .buffer_bytes_max = RT5677_BUF_BYTES_TOTAL, 848c2ecf20Sopenharmony_ci}; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic struct snd_soc_dai_driver rt5677_spi_dai = { 878c2ecf20Sopenharmony_ci /* The DAI name "rt5677-dsp-cpu-dai" is not used. The actual DAI name 888c2ecf20Sopenharmony_ci * registered with ASoC is the name of the device "spi-RT5677AA:00", 898c2ecf20Sopenharmony_ci * because we only have one DAI. See snd_soc_register_dais(). 908c2ecf20Sopenharmony_ci */ 918c2ecf20Sopenharmony_ci .name = "rt5677-dsp-cpu-dai", 928c2ecf20Sopenharmony_ci .id = 0, 938c2ecf20Sopenharmony_ci .capture = { 948c2ecf20Sopenharmony_ci .stream_name = "DSP Capture", 958c2ecf20Sopenharmony_ci .channels_min = 1, 968c2ecf20Sopenharmony_ci .channels_max = 1, 978c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_16000, 988c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, 998c2ecf20Sopenharmony_ci }, 1008c2ecf20Sopenharmony_ci}; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci/* PCM for streaming audio from the DSP buffer */ 1038c2ecf20Sopenharmony_cistatic int rt5677_spi_pcm_open( 1048c2ecf20Sopenharmony_ci struct snd_soc_component *component, 1058c2ecf20Sopenharmony_ci struct snd_pcm_substream *substream) 1068c2ecf20Sopenharmony_ci{ 1078c2ecf20Sopenharmony_ci snd_soc_set_runtime_hwparams(substream, &rt5677_spi_pcm_hardware); 1088c2ecf20Sopenharmony_ci return 0; 1098c2ecf20Sopenharmony_ci} 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cistatic int rt5677_spi_pcm_close( 1128c2ecf20Sopenharmony_ci struct snd_soc_component *component, 1138c2ecf20Sopenharmony_ci struct snd_pcm_substream *substream) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 1168c2ecf20Sopenharmony_ci struct snd_soc_component *codec_component = 1178c2ecf20Sopenharmony_ci snd_soc_rtdcom_lookup(rtd, "rt5677"); 1188c2ecf20Sopenharmony_ci struct rt5677_priv *rt5677 = 1198c2ecf20Sopenharmony_ci snd_soc_component_get_drvdata(codec_component); 1208c2ecf20Sopenharmony_ci struct rt5677_dsp *rt5677_dsp = 1218c2ecf20Sopenharmony_ci snd_soc_component_get_drvdata(component); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci cancel_delayed_work_sync(&rt5677_dsp->copy_work); 1248c2ecf20Sopenharmony_ci rt5677->set_dsp_vad(codec_component, false); 1258c2ecf20Sopenharmony_ci return 0; 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic int rt5677_spi_hw_params( 1298c2ecf20Sopenharmony_ci struct snd_soc_component *component, 1308c2ecf20Sopenharmony_ci struct snd_pcm_substream *substream, 1318c2ecf20Sopenharmony_ci struct snd_pcm_hw_params *hw_params) 1328c2ecf20Sopenharmony_ci{ 1338c2ecf20Sopenharmony_ci struct rt5677_dsp *rt5677_dsp = 1348c2ecf20Sopenharmony_ci snd_soc_component_get_drvdata(component); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci mutex_lock(&rt5677_dsp->dma_lock); 1378c2ecf20Sopenharmony_ci rt5677_dsp->substream = substream; 1388c2ecf20Sopenharmony_ci mutex_unlock(&rt5677_dsp->dma_lock); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci return 0; 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cistatic int rt5677_spi_hw_free( 1448c2ecf20Sopenharmony_ci struct snd_soc_component *component, 1458c2ecf20Sopenharmony_ci struct snd_pcm_substream *substream) 1468c2ecf20Sopenharmony_ci{ 1478c2ecf20Sopenharmony_ci struct rt5677_dsp *rt5677_dsp = 1488c2ecf20Sopenharmony_ci snd_soc_component_get_drvdata(component); 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci mutex_lock(&rt5677_dsp->dma_lock); 1518c2ecf20Sopenharmony_ci rt5677_dsp->substream = NULL; 1528c2ecf20Sopenharmony_ci mutex_unlock(&rt5677_dsp->dma_lock); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci return 0; 1558c2ecf20Sopenharmony_ci} 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_cistatic int rt5677_spi_prepare( 1588c2ecf20Sopenharmony_ci struct snd_soc_component *component, 1598c2ecf20Sopenharmony_ci struct snd_pcm_substream *substream) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 1628c2ecf20Sopenharmony_ci struct snd_soc_component *rt5677_component = 1638c2ecf20Sopenharmony_ci snd_soc_rtdcom_lookup(rtd, "rt5677"); 1648c2ecf20Sopenharmony_ci struct rt5677_priv *rt5677 = 1658c2ecf20Sopenharmony_ci snd_soc_component_get_drvdata(rt5677_component); 1668c2ecf20Sopenharmony_ci struct rt5677_dsp *rt5677_dsp = 1678c2ecf20Sopenharmony_ci snd_soc_component_get_drvdata(component); 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci rt5677->set_dsp_vad(rt5677_component, true); 1708c2ecf20Sopenharmony_ci rt5677_dsp->dma_offset = 0; 1718c2ecf20Sopenharmony_ci rt5677_dsp->avail_bytes = 0; 1728c2ecf20Sopenharmony_ci return 0; 1738c2ecf20Sopenharmony_ci} 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_cistatic snd_pcm_uframes_t rt5677_spi_pcm_pointer( 1768c2ecf20Sopenharmony_ci struct snd_soc_component *component, 1778c2ecf20Sopenharmony_ci struct snd_pcm_substream *substream) 1788c2ecf20Sopenharmony_ci{ 1798c2ecf20Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 1808c2ecf20Sopenharmony_ci struct rt5677_dsp *rt5677_dsp = 1818c2ecf20Sopenharmony_ci snd_soc_component_get_drvdata(component); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci return bytes_to_frames(runtime, rt5677_dsp->dma_offset); 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cistatic int rt5677_spi_mic_write_offset(u32 *mic_write_offset) 1878c2ecf20Sopenharmony_ci{ 1888c2ecf20Sopenharmony_ci int ret; 1898c2ecf20Sopenharmony_ci /* Grab the first 4 bytes that hold the write pointer on the 1908c2ecf20Sopenharmony_ci * dsp, and check to make sure that it points somewhere inside the 1918c2ecf20Sopenharmony_ci * buffer. 1928c2ecf20Sopenharmony_ci */ 1938c2ecf20Sopenharmony_ci ret = rt5677_spi_read(RT5677_MIC_BUF_ADDR, mic_write_offset, 1948c2ecf20Sopenharmony_ci sizeof(u32)); 1958c2ecf20Sopenharmony_ci if (ret) 1968c2ecf20Sopenharmony_ci return ret; 1978c2ecf20Sopenharmony_ci /* Adjust the offset so that it's zero-based */ 1988c2ecf20Sopenharmony_ci *mic_write_offset = *mic_write_offset - sizeof(u32); 1998c2ecf20Sopenharmony_ci return *mic_write_offset < RT5677_MIC_BUF_BYTES ? 0 : -EFAULT; 2008c2ecf20Sopenharmony_ci} 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci/* 2038c2ecf20Sopenharmony_ci * Copy one contiguous block of audio samples from the DSP mic buffer to the 2048c2ecf20Sopenharmony_ci * dma_area of the pcm runtime. The receiving buffer may wrap around. 2058c2ecf20Sopenharmony_ci * @begin: start offset of the block to copy, in bytes. 2068c2ecf20Sopenharmony_ci * @end: offset of the first byte after the block to copy, must be greater 2078c2ecf20Sopenharmony_ci * than or equal to begin. 2088c2ecf20Sopenharmony_ci * 2098c2ecf20Sopenharmony_ci * Return: Zero if successful, or a negative error code on failure. 2108c2ecf20Sopenharmony_ci */ 2118c2ecf20Sopenharmony_cistatic int rt5677_spi_copy_block(struct rt5677_dsp *rt5677_dsp, 2128c2ecf20Sopenharmony_ci u32 begin, u32 end) 2138c2ecf20Sopenharmony_ci{ 2148c2ecf20Sopenharmony_ci struct snd_pcm_runtime *runtime = rt5677_dsp->substream->runtime; 2158c2ecf20Sopenharmony_ci size_t bytes_per_frame = frames_to_bytes(runtime, 1); 2168c2ecf20Sopenharmony_ci size_t first_chunk_len, second_chunk_len; 2178c2ecf20Sopenharmony_ci int ret; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci if (begin > end || runtime->dma_bytes < 2 * bytes_per_frame) { 2208c2ecf20Sopenharmony_ci dev_err(rt5677_dsp->dev, 2218c2ecf20Sopenharmony_ci "Invalid copy from (%u, %u), dma_area size %zu\n", 2228c2ecf20Sopenharmony_ci begin, end, runtime->dma_bytes); 2238c2ecf20Sopenharmony_ci return -EINVAL; 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci /* The block to copy is empty */ 2278c2ecf20Sopenharmony_ci if (begin == end) 2288c2ecf20Sopenharmony_ci return 0; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci /* If the incoming chunk is too big for the receiving buffer, only the 2318c2ecf20Sopenharmony_ci * last "receiving buffer size - one frame" bytes are copied. 2328c2ecf20Sopenharmony_ci */ 2338c2ecf20Sopenharmony_ci if (end - begin > runtime->dma_bytes - bytes_per_frame) 2348c2ecf20Sopenharmony_ci begin = end - (runtime->dma_bytes - bytes_per_frame); 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci /* May need to split to two chunks, calculate the size of each */ 2378c2ecf20Sopenharmony_ci first_chunk_len = end - begin; 2388c2ecf20Sopenharmony_ci second_chunk_len = 0; 2398c2ecf20Sopenharmony_ci if (rt5677_dsp->dma_offset + first_chunk_len > runtime->dma_bytes) { 2408c2ecf20Sopenharmony_ci /* Receiving buffer wrapped around */ 2418c2ecf20Sopenharmony_ci second_chunk_len = first_chunk_len; 2428c2ecf20Sopenharmony_ci first_chunk_len = runtime->dma_bytes - rt5677_dsp->dma_offset; 2438c2ecf20Sopenharmony_ci second_chunk_len -= first_chunk_len; 2448c2ecf20Sopenharmony_ci } 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci /* Copy first chunk */ 2478c2ecf20Sopenharmony_ci ret = rt5677_spi_read(RT5677_MIC_BUF_ADDR + sizeof(u32) + begin, 2488c2ecf20Sopenharmony_ci runtime->dma_area + rt5677_dsp->dma_offset, 2498c2ecf20Sopenharmony_ci first_chunk_len); 2508c2ecf20Sopenharmony_ci if (ret) 2518c2ecf20Sopenharmony_ci return ret; 2528c2ecf20Sopenharmony_ci rt5677_dsp->dma_offset += first_chunk_len; 2538c2ecf20Sopenharmony_ci if (rt5677_dsp->dma_offset == runtime->dma_bytes) 2548c2ecf20Sopenharmony_ci rt5677_dsp->dma_offset = 0; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci /* Copy second chunk */ 2578c2ecf20Sopenharmony_ci if (second_chunk_len) { 2588c2ecf20Sopenharmony_ci ret = rt5677_spi_read(RT5677_MIC_BUF_ADDR + sizeof(u32) + 2598c2ecf20Sopenharmony_ci begin + first_chunk_len, runtime->dma_area, 2608c2ecf20Sopenharmony_ci second_chunk_len); 2618c2ecf20Sopenharmony_ci if (!ret) 2628c2ecf20Sopenharmony_ci rt5677_dsp->dma_offset = second_chunk_len; 2638c2ecf20Sopenharmony_ci } 2648c2ecf20Sopenharmony_ci return ret; 2658c2ecf20Sopenharmony_ci} 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci/* 2688c2ecf20Sopenharmony_ci * Copy a given amount of audio samples from the DSP mic buffer starting at 2698c2ecf20Sopenharmony_ci * mic_read_offset, to the dma_area of the pcm runtime. The source buffer may 2708c2ecf20Sopenharmony_ci * wrap around. mic_read_offset is updated after successful copy. 2718c2ecf20Sopenharmony_ci * @amount: amount of samples to copy, in bytes. 2728c2ecf20Sopenharmony_ci * 2738c2ecf20Sopenharmony_ci * Return: Zero if successful, or a negative error code on failure. 2748c2ecf20Sopenharmony_ci */ 2758c2ecf20Sopenharmony_cistatic int rt5677_spi_copy(struct rt5677_dsp *rt5677_dsp, u32 amount) 2768c2ecf20Sopenharmony_ci{ 2778c2ecf20Sopenharmony_ci int ret = 0; 2788c2ecf20Sopenharmony_ci u32 target; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci if (amount == 0) 2818c2ecf20Sopenharmony_ci return ret; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci target = rt5677_dsp->mic_read_offset + amount; 2848c2ecf20Sopenharmony_ci /* Copy the first chunk in DSP's mic buffer */ 2858c2ecf20Sopenharmony_ci ret |= rt5677_spi_copy_block(rt5677_dsp, rt5677_dsp->mic_read_offset, 2868c2ecf20Sopenharmony_ci min(target, RT5677_MIC_BUF_BYTES)); 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci if (target >= RT5677_MIC_BUF_BYTES) { 2898c2ecf20Sopenharmony_ci /* Wrap around, copy the second chunk */ 2908c2ecf20Sopenharmony_ci target -= RT5677_MIC_BUF_BYTES; 2918c2ecf20Sopenharmony_ci ret |= rt5677_spi_copy_block(rt5677_dsp, 0, target); 2928c2ecf20Sopenharmony_ci } 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci if (!ret) 2958c2ecf20Sopenharmony_ci rt5677_dsp->mic_read_offset = target; 2968c2ecf20Sopenharmony_ci return ret; 2978c2ecf20Sopenharmony_ci} 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci/* 3008c2ecf20Sopenharmony_ci * A delayed work that streams audio samples from the DSP mic buffer to the 3018c2ecf20Sopenharmony_ci * dma_area of the pcm runtime via SPI. 3028c2ecf20Sopenharmony_ci */ 3038c2ecf20Sopenharmony_cistatic void rt5677_spi_copy_work(struct work_struct *work) 3048c2ecf20Sopenharmony_ci{ 3058c2ecf20Sopenharmony_ci struct rt5677_dsp *rt5677_dsp = 3068c2ecf20Sopenharmony_ci container_of(work, struct rt5677_dsp, copy_work.work); 3078c2ecf20Sopenharmony_ci struct snd_pcm_runtime *runtime; 3088c2ecf20Sopenharmony_ci u32 mic_write_offset; 3098c2ecf20Sopenharmony_ci size_t new_bytes, copy_bytes, period_bytes; 3108c2ecf20Sopenharmony_ci unsigned int delay; 3118c2ecf20Sopenharmony_ci int ret = 0; 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci /* Ensure runtime->dma_area buffer does not go away while copying. */ 3148c2ecf20Sopenharmony_ci mutex_lock(&rt5677_dsp->dma_lock); 3158c2ecf20Sopenharmony_ci if (!rt5677_dsp->substream) { 3168c2ecf20Sopenharmony_ci dev_err(rt5677_dsp->dev, "No pcm substream\n"); 3178c2ecf20Sopenharmony_ci goto done; 3188c2ecf20Sopenharmony_ci } 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci runtime = rt5677_dsp->substream->runtime; 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci if (rt5677_spi_mic_write_offset(&mic_write_offset)) { 3238c2ecf20Sopenharmony_ci dev_err(rt5677_dsp->dev, "No mic_write_offset\n"); 3248c2ecf20Sopenharmony_ci goto done; 3258c2ecf20Sopenharmony_ci } 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci /* If this is the first time that we've asked for streaming data after 3288c2ecf20Sopenharmony_ci * a hotword is fired, we should start reading from the previous 2 3298c2ecf20Sopenharmony_ci * seconds of audio from wherever the mic_write_offset is currently. 3308c2ecf20Sopenharmony_ci */ 3318c2ecf20Sopenharmony_ci if (rt5677_dsp->new_hotword) { 3328c2ecf20Sopenharmony_ci rt5677_dsp->new_hotword = false; 3338c2ecf20Sopenharmony_ci /* See if buffer wraparound happens */ 3348c2ecf20Sopenharmony_ci if (mic_write_offset < RT5677_MIC_BUF_FIRST_READ_SIZE) 3358c2ecf20Sopenharmony_ci rt5677_dsp->mic_read_offset = RT5677_MIC_BUF_BYTES - 3368c2ecf20Sopenharmony_ci (RT5677_MIC_BUF_FIRST_READ_SIZE - 3378c2ecf20Sopenharmony_ci mic_write_offset); 3388c2ecf20Sopenharmony_ci else 3398c2ecf20Sopenharmony_ci rt5677_dsp->mic_read_offset = mic_write_offset - 3408c2ecf20Sopenharmony_ci RT5677_MIC_BUF_FIRST_READ_SIZE; 3418c2ecf20Sopenharmony_ci } 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci /* Calculate the amount of new samples in bytes */ 3448c2ecf20Sopenharmony_ci if (rt5677_dsp->mic_read_offset <= mic_write_offset) 3458c2ecf20Sopenharmony_ci new_bytes = mic_write_offset - rt5677_dsp->mic_read_offset; 3468c2ecf20Sopenharmony_ci else 3478c2ecf20Sopenharmony_ci new_bytes = RT5677_MIC_BUF_BYTES + mic_write_offset 3488c2ecf20Sopenharmony_ci - rt5677_dsp->mic_read_offset; 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci /* Copy all new samples from DSP mic buffer, one period at a time */ 3518c2ecf20Sopenharmony_ci period_bytes = snd_pcm_lib_period_bytes(rt5677_dsp->substream); 3528c2ecf20Sopenharmony_ci while (new_bytes) { 3538c2ecf20Sopenharmony_ci copy_bytes = min(new_bytes, period_bytes 3548c2ecf20Sopenharmony_ci - rt5677_dsp->avail_bytes); 3558c2ecf20Sopenharmony_ci ret = rt5677_spi_copy(rt5677_dsp, copy_bytes); 3568c2ecf20Sopenharmony_ci if (ret) { 3578c2ecf20Sopenharmony_ci dev_err(rt5677_dsp->dev, "Copy failed %d\n", ret); 3588c2ecf20Sopenharmony_ci goto done; 3598c2ecf20Sopenharmony_ci } 3608c2ecf20Sopenharmony_ci rt5677_dsp->avail_bytes += copy_bytes; 3618c2ecf20Sopenharmony_ci if (rt5677_dsp->avail_bytes >= period_bytes) { 3628c2ecf20Sopenharmony_ci snd_pcm_period_elapsed(rt5677_dsp->substream); 3638c2ecf20Sopenharmony_ci rt5677_dsp->avail_bytes = 0; 3648c2ecf20Sopenharmony_ci } 3658c2ecf20Sopenharmony_ci new_bytes -= copy_bytes; 3668c2ecf20Sopenharmony_ci } 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ci delay = bytes_to_frames(runtime, period_bytes) / (runtime->rate / 1000); 3698c2ecf20Sopenharmony_ci schedule_delayed_work(&rt5677_dsp->copy_work, msecs_to_jiffies(delay)); 3708c2ecf20Sopenharmony_cidone: 3718c2ecf20Sopenharmony_ci mutex_unlock(&rt5677_dsp->dma_lock); 3728c2ecf20Sopenharmony_ci} 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_cistatic int rt5677_spi_pcm_new(struct snd_soc_component *component, 3758c2ecf20Sopenharmony_ci struct snd_soc_pcm_runtime *rtd) 3768c2ecf20Sopenharmony_ci{ 3778c2ecf20Sopenharmony_ci snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_VMALLOC, 3788c2ecf20Sopenharmony_ci NULL, 0, 0); 3798c2ecf20Sopenharmony_ci return 0; 3808c2ecf20Sopenharmony_ci} 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_cistatic int rt5677_spi_pcm_probe(struct snd_soc_component *component) 3838c2ecf20Sopenharmony_ci{ 3848c2ecf20Sopenharmony_ci struct rt5677_dsp *rt5677_dsp; 3858c2ecf20Sopenharmony_ci 3868c2ecf20Sopenharmony_ci rt5677_dsp = devm_kzalloc(component->dev, sizeof(*rt5677_dsp), 3878c2ecf20Sopenharmony_ci GFP_KERNEL); 3888c2ecf20Sopenharmony_ci if (!rt5677_dsp) 3898c2ecf20Sopenharmony_ci return -ENOMEM; 3908c2ecf20Sopenharmony_ci rt5677_dsp->dev = &g_spi->dev; 3918c2ecf20Sopenharmony_ci mutex_init(&rt5677_dsp->dma_lock); 3928c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&rt5677_dsp->copy_work, rt5677_spi_copy_work); 3938c2ecf20Sopenharmony_ci 3948c2ecf20Sopenharmony_ci snd_soc_component_set_drvdata(component, rt5677_dsp); 3958c2ecf20Sopenharmony_ci return 0; 3968c2ecf20Sopenharmony_ci} 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_cistatic const struct snd_soc_component_driver rt5677_spi_dai_component = { 3998c2ecf20Sopenharmony_ci .name = DRV_NAME, 4008c2ecf20Sopenharmony_ci .probe = rt5677_spi_pcm_probe, 4018c2ecf20Sopenharmony_ci .open = rt5677_spi_pcm_open, 4028c2ecf20Sopenharmony_ci .close = rt5677_spi_pcm_close, 4038c2ecf20Sopenharmony_ci .hw_params = rt5677_spi_hw_params, 4048c2ecf20Sopenharmony_ci .hw_free = rt5677_spi_hw_free, 4058c2ecf20Sopenharmony_ci .prepare = rt5677_spi_prepare, 4068c2ecf20Sopenharmony_ci .pointer = rt5677_spi_pcm_pointer, 4078c2ecf20Sopenharmony_ci .pcm_construct = rt5677_spi_pcm_new, 4088c2ecf20Sopenharmony_ci}; 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_ci/* Select a suitable transfer command for the next transfer to ensure 4118c2ecf20Sopenharmony_ci * the transfer address is always naturally aligned while minimizing 4128c2ecf20Sopenharmony_ci * the total number of transfers required. 4138c2ecf20Sopenharmony_ci * 4148c2ecf20Sopenharmony_ci * 3 transfer commands are available: 4158c2ecf20Sopenharmony_ci * RT5677_SPI_READ/WRITE_16: Transfer 2 bytes 4168c2ecf20Sopenharmony_ci * RT5677_SPI_READ/WRITE_32: Transfer 4 bytes 4178c2ecf20Sopenharmony_ci * RT5677_SPI_READ/WRITE_BURST: Transfer any multiples of 8 bytes 4188c2ecf20Sopenharmony_ci * 4198c2ecf20Sopenharmony_ci * Note: 4208c2ecf20Sopenharmony_ci * 16 Bit writes and reads are restricted to the address range 4218c2ecf20Sopenharmony_ci * 0x18020000 ~ 0x18021000 4228c2ecf20Sopenharmony_ci * 4238c2ecf20Sopenharmony_ci * For example, reading 256 bytes at 0x60030004 uses the following commands: 4248c2ecf20Sopenharmony_ci * 0x60030004 RT5677_SPI_READ_32 4 bytes 4258c2ecf20Sopenharmony_ci * 0x60030008 RT5677_SPI_READ_BURST 240 bytes 4268c2ecf20Sopenharmony_ci * 0x600300F8 RT5677_SPI_READ_BURST 8 bytes 4278c2ecf20Sopenharmony_ci * 0x60030100 RT5677_SPI_READ_32 4 bytes 4288c2ecf20Sopenharmony_ci * 4298c2ecf20Sopenharmony_ci * Input: 4308c2ecf20Sopenharmony_ci * @read: true for read commands; false for write commands 4318c2ecf20Sopenharmony_ci * @align: alignment of the next transfer address 4328c2ecf20Sopenharmony_ci * @remain: number of bytes remaining to transfer 4338c2ecf20Sopenharmony_ci * 4348c2ecf20Sopenharmony_ci * Output: 4358c2ecf20Sopenharmony_ci * @len: number of bytes to transfer with the selected command 4368c2ecf20Sopenharmony_ci * Returns the selected command 4378c2ecf20Sopenharmony_ci */ 4388c2ecf20Sopenharmony_cistatic u8 rt5677_spi_select_cmd(bool read, u32 align, u32 remain, u32 *len) 4398c2ecf20Sopenharmony_ci{ 4408c2ecf20Sopenharmony_ci u8 cmd; 4418c2ecf20Sopenharmony_ci 4428c2ecf20Sopenharmony_ci if (align == 4 || remain <= 4) { 4438c2ecf20Sopenharmony_ci cmd = RT5677_SPI_READ_32; 4448c2ecf20Sopenharmony_ci *len = 4; 4458c2ecf20Sopenharmony_ci } else { 4468c2ecf20Sopenharmony_ci cmd = RT5677_SPI_READ_BURST; 4478c2ecf20Sopenharmony_ci *len = (((remain - 1) >> 3) + 1) << 3; 4488c2ecf20Sopenharmony_ci *len = min_t(u32, *len, RT5677_SPI_BURST_LEN); 4498c2ecf20Sopenharmony_ci } 4508c2ecf20Sopenharmony_ci return read ? cmd : cmd + 1; 4518c2ecf20Sopenharmony_ci} 4528c2ecf20Sopenharmony_ci 4538c2ecf20Sopenharmony_ci/* Copy dstlen bytes from src to dst, while reversing byte order for each word. 4548c2ecf20Sopenharmony_ci * If srclen < dstlen, zeros are padded. 4558c2ecf20Sopenharmony_ci */ 4568c2ecf20Sopenharmony_cistatic void rt5677_spi_reverse(u8 *dst, u32 dstlen, const u8 *src, u32 srclen) 4578c2ecf20Sopenharmony_ci{ 4588c2ecf20Sopenharmony_ci u32 w, i, si; 4598c2ecf20Sopenharmony_ci u32 word_size = min_t(u32, dstlen, 8); 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci for (w = 0; w < dstlen; w += word_size) { 4628c2ecf20Sopenharmony_ci for (i = 0; i < word_size && i + w < dstlen; i++) { 4638c2ecf20Sopenharmony_ci si = w + word_size - i - 1; 4648c2ecf20Sopenharmony_ci dst[w + i] = si < srclen ? src[si] : 0; 4658c2ecf20Sopenharmony_ci } 4668c2ecf20Sopenharmony_ci } 4678c2ecf20Sopenharmony_ci} 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci/* Read DSP address space using SPI. addr and len have to be 4-byte aligned. */ 4708c2ecf20Sopenharmony_ciint rt5677_spi_read(u32 addr, void *rxbuf, size_t len) 4718c2ecf20Sopenharmony_ci{ 4728c2ecf20Sopenharmony_ci u32 offset; 4738c2ecf20Sopenharmony_ci int status = 0; 4748c2ecf20Sopenharmony_ci struct spi_transfer t[2]; 4758c2ecf20Sopenharmony_ci struct spi_message m; 4768c2ecf20Sopenharmony_ci /* +4 bytes is for the DummyPhase following the AddressPhase */ 4778c2ecf20Sopenharmony_ci u8 header[RT5677_SPI_HEADER + 4]; 4788c2ecf20Sopenharmony_ci u8 body[RT5677_SPI_BURST_LEN]; 4798c2ecf20Sopenharmony_ci u8 spi_cmd; 4808c2ecf20Sopenharmony_ci u8 *cb = rxbuf; 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci if (!g_spi) 4838c2ecf20Sopenharmony_ci return -ENODEV; 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci if ((addr & 3) || (len & 3)) { 4868c2ecf20Sopenharmony_ci dev_err(&g_spi->dev, "Bad read align 0x%x(%zu)\n", addr, len); 4878c2ecf20Sopenharmony_ci return -EACCES; 4888c2ecf20Sopenharmony_ci } 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci memset(t, 0, sizeof(t)); 4918c2ecf20Sopenharmony_ci t[0].tx_buf = header; 4928c2ecf20Sopenharmony_ci t[0].len = sizeof(header); 4938c2ecf20Sopenharmony_ci t[0].speed_hz = RT5677_SPI_FREQ; 4948c2ecf20Sopenharmony_ci t[1].rx_buf = body; 4958c2ecf20Sopenharmony_ci t[1].speed_hz = RT5677_SPI_FREQ; 4968c2ecf20Sopenharmony_ci spi_message_init_with_transfers(&m, t, ARRAY_SIZE(t)); 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci for (offset = 0; offset < len; offset += t[1].len) { 4998c2ecf20Sopenharmony_ci spi_cmd = rt5677_spi_select_cmd(true, (addr + offset) & 7, 5008c2ecf20Sopenharmony_ci len - offset, &t[1].len); 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci /* Construct SPI message header */ 5038c2ecf20Sopenharmony_ci header[0] = spi_cmd; 5048c2ecf20Sopenharmony_ci header[1] = ((addr + offset) & 0xff000000) >> 24; 5058c2ecf20Sopenharmony_ci header[2] = ((addr + offset) & 0x00ff0000) >> 16; 5068c2ecf20Sopenharmony_ci header[3] = ((addr + offset) & 0x0000ff00) >> 8; 5078c2ecf20Sopenharmony_ci header[4] = ((addr + offset) & 0x000000ff) >> 0; 5088c2ecf20Sopenharmony_ci 5098c2ecf20Sopenharmony_ci mutex_lock(&spi_mutex); 5108c2ecf20Sopenharmony_ci status |= spi_sync(g_spi, &m); 5118c2ecf20Sopenharmony_ci mutex_unlock(&spi_mutex); 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci /* Copy data back to caller buffer */ 5158c2ecf20Sopenharmony_ci rt5677_spi_reverse(cb + offset, len - offset, body, t[1].len); 5168c2ecf20Sopenharmony_ci } 5178c2ecf20Sopenharmony_ci return status; 5188c2ecf20Sopenharmony_ci} 5198c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rt5677_spi_read); 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci/* Write DSP address space using SPI. addr has to be 4-byte aligned. 5228c2ecf20Sopenharmony_ci * If len is not 4-byte aligned, then extra zeros are written at the end 5238c2ecf20Sopenharmony_ci * as padding. 5248c2ecf20Sopenharmony_ci */ 5258c2ecf20Sopenharmony_ciint rt5677_spi_write(u32 addr, const void *txbuf, size_t len) 5268c2ecf20Sopenharmony_ci{ 5278c2ecf20Sopenharmony_ci u32 offset; 5288c2ecf20Sopenharmony_ci int status = 0; 5298c2ecf20Sopenharmony_ci struct spi_transfer t; 5308c2ecf20Sopenharmony_ci struct spi_message m; 5318c2ecf20Sopenharmony_ci /* +1 byte is for the DummyPhase following the DataPhase */ 5328c2ecf20Sopenharmony_ci u8 buf[RT5677_SPI_HEADER + RT5677_SPI_BURST_LEN + 1]; 5338c2ecf20Sopenharmony_ci u8 *body = buf + RT5677_SPI_HEADER; 5348c2ecf20Sopenharmony_ci u8 spi_cmd; 5358c2ecf20Sopenharmony_ci const u8 *cb = txbuf; 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_ci if (!g_spi) 5388c2ecf20Sopenharmony_ci return -ENODEV; 5398c2ecf20Sopenharmony_ci 5408c2ecf20Sopenharmony_ci if (addr & 3) { 5418c2ecf20Sopenharmony_ci dev_err(&g_spi->dev, "Bad write align 0x%x(%zu)\n", addr, len); 5428c2ecf20Sopenharmony_ci return -EACCES; 5438c2ecf20Sopenharmony_ci } 5448c2ecf20Sopenharmony_ci 5458c2ecf20Sopenharmony_ci memset(&t, 0, sizeof(t)); 5468c2ecf20Sopenharmony_ci t.tx_buf = buf; 5478c2ecf20Sopenharmony_ci t.speed_hz = RT5677_SPI_FREQ; 5488c2ecf20Sopenharmony_ci spi_message_init_with_transfers(&m, &t, 1); 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci for (offset = 0; offset < len;) { 5518c2ecf20Sopenharmony_ci spi_cmd = rt5677_spi_select_cmd(false, (addr + offset) & 7, 5528c2ecf20Sopenharmony_ci len - offset, &t.len); 5538c2ecf20Sopenharmony_ci 5548c2ecf20Sopenharmony_ci /* Construct SPI message header */ 5558c2ecf20Sopenharmony_ci buf[0] = spi_cmd; 5568c2ecf20Sopenharmony_ci buf[1] = ((addr + offset) & 0xff000000) >> 24; 5578c2ecf20Sopenharmony_ci buf[2] = ((addr + offset) & 0x00ff0000) >> 16; 5588c2ecf20Sopenharmony_ci buf[3] = ((addr + offset) & 0x0000ff00) >> 8; 5598c2ecf20Sopenharmony_ci buf[4] = ((addr + offset) & 0x000000ff) >> 0; 5608c2ecf20Sopenharmony_ci 5618c2ecf20Sopenharmony_ci /* Fetch data from caller buffer */ 5628c2ecf20Sopenharmony_ci rt5677_spi_reverse(body, t.len, cb + offset, len - offset); 5638c2ecf20Sopenharmony_ci offset += t.len; 5648c2ecf20Sopenharmony_ci t.len += RT5677_SPI_HEADER + 1; 5658c2ecf20Sopenharmony_ci 5668c2ecf20Sopenharmony_ci mutex_lock(&spi_mutex); 5678c2ecf20Sopenharmony_ci status |= spi_sync(g_spi, &m); 5688c2ecf20Sopenharmony_ci mutex_unlock(&spi_mutex); 5698c2ecf20Sopenharmony_ci } 5708c2ecf20Sopenharmony_ci return status; 5718c2ecf20Sopenharmony_ci} 5728c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rt5677_spi_write); 5738c2ecf20Sopenharmony_ci 5748c2ecf20Sopenharmony_ciint rt5677_spi_write_firmware(u32 addr, const struct firmware *fw) 5758c2ecf20Sopenharmony_ci{ 5768c2ecf20Sopenharmony_ci return rt5677_spi_write(addr, fw->data, fw->size); 5778c2ecf20Sopenharmony_ci} 5788c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rt5677_spi_write_firmware); 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_civoid rt5677_spi_hotword_detected(void) 5818c2ecf20Sopenharmony_ci{ 5828c2ecf20Sopenharmony_ci struct rt5677_dsp *rt5677_dsp; 5838c2ecf20Sopenharmony_ci 5848c2ecf20Sopenharmony_ci if (!g_spi) 5858c2ecf20Sopenharmony_ci return; 5868c2ecf20Sopenharmony_ci 5878c2ecf20Sopenharmony_ci rt5677_dsp = dev_get_drvdata(&g_spi->dev); 5888c2ecf20Sopenharmony_ci if (!rt5677_dsp) { 5898c2ecf20Sopenharmony_ci dev_err(&g_spi->dev, "Can't get rt5677_dsp\n"); 5908c2ecf20Sopenharmony_ci return; 5918c2ecf20Sopenharmony_ci } 5928c2ecf20Sopenharmony_ci 5938c2ecf20Sopenharmony_ci mutex_lock(&rt5677_dsp->dma_lock); 5948c2ecf20Sopenharmony_ci dev_info(rt5677_dsp->dev, "Hotword detected\n"); 5958c2ecf20Sopenharmony_ci rt5677_dsp->new_hotword = true; 5968c2ecf20Sopenharmony_ci mutex_unlock(&rt5677_dsp->dma_lock); 5978c2ecf20Sopenharmony_ci 5988c2ecf20Sopenharmony_ci schedule_delayed_work(&rt5677_dsp->copy_work, 0); 5998c2ecf20Sopenharmony_ci} 6008c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rt5677_spi_hotword_detected); 6018c2ecf20Sopenharmony_ci 6028c2ecf20Sopenharmony_cistatic int rt5677_spi_probe(struct spi_device *spi) 6038c2ecf20Sopenharmony_ci{ 6048c2ecf20Sopenharmony_ci int ret; 6058c2ecf20Sopenharmony_ci 6068c2ecf20Sopenharmony_ci g_spi = spi; 6078c2ecf20Sopenharmony_ci 6088c2ecf20Sopenharmony_ci ret = devm_snd_soc_register_component(&spi->dev, 6098c2ecf20Sopenharmony_ci &rt5677_spi_dai_component, 6108c2ecf20Sopenharmony_ci &rt5677_spi_dai, 1); 6118c2ecf20Sopenharmony_ci if (ret < 0) 6128c2ecf20Sopenharmony_ci dev_err(&spi->dev, "Failed to register component.\n"); 6138c2ecf20Sopenharmony_ci 6148c2ecf20Sopenharmony_ci return ret; 6158c2ecf20Sopenharmony_ci} 6168c2ecf20Sopenharmony_ci 6178c2ecf20Sopenharmony_ci#ifdef CONFIG_ACPI 6188c2ecf20Sopenharmony_cistatic const struct acpi_device_id rt5677_spi_acpi_id[] = { 6198c2ecf20Sopenharmony_ci { "RT5677AA", 0 }, 6208c2ecf20Sopenharmony_ci { } 6218c2ecf20Sopenharmony_ci}; 6228c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, rt5677_spi_acpi_id); 6238c2ecf20Sopenharmony_ci#endif 6248c2ecf20Sopenharmony_ci 6258c2ecf20Sopenharmony_cistatic struct spi_driver rt5677_spi_driver = { 6268c2ecf20Sopenharmony_ci .driver = { 6278c2ecf20Sopenharmony_ci .name = DRV_NAME, 6288c2ecf20Sopenharmony_ci .acpi_match_table = ACPI_PTR(rt5677_spi_acpi_id), 6298c2ecf20Sopenharmony_ci }, 6308c2ecf20Sopenharmony_ci .probe = rt5677_spi_probe, 6318c2ecf20Sopenharmony_ci}; 6328c2ecf20Sopenharmony_cimodule_spi_driver(rt5677_spi_driver); 6338c2ecf20Sopenharmony_ci 6348c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ASoC RT5677 SPI driver"); 6358c2ecf20Sopenharmony_ciMODULE_AUTHOR("Oder Chiou <oder_chiou@realtek.com>"); 6368c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 637