18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * cxd2880-spi.c 48c2ecf20Sopenharmony_ci * Sony CXD2880 DVB-T2/T tuner + demodulator driver 58c2ecf20Sopenharmony_ci * SPI adapter 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Copyright (C) 2016, 2017, 2018 Sony Semiconductor Solutions Corporation 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/spi/spi.h> 138c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h> 148c2ecf20Sopenharmony_ci#include <linux/ktime.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <media/dvb_demux.h> 178c2ecf20Sopenharmony_ci#include <media/dmxdev.h> 188c2ecf20Sopenharmony_ci#include <media/dvb_frontend.h> 198c2ecf20Sopenharmony_ci#include "cxd2880.h" 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define CXD2880_MAX_FILTER_SIZE 32 228c2ecf20Sopenharmony_ci#define BURST_WRITE_MAX 128 238c2ecf20Sopenharmony_ci#define MAX_TRANS_PKT 300 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistruct cxd2880_ts_buf_info { 268c2ecf20Sopenharmony_ci u8 read_ready:1; 278c2ecf20Sopenharmony_ci u8 almost_full:1; 288c2ecf20Sopenharmony_ci u8 almost_empty:1; 298c2ecf20Sopenharmony_ci u8 overflow:1; 308c2ecf20Sopenharmony_ci u8 underflow:1; 318c2ecf20Sopenharmony_ci u16 pkt_num; 328c2ecf20Sopenharmony_ci}; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistruct cxd2880_pid_config { 358c2ecf20Sopenharmony_ci u8 is_enable; 368c2ecf20Sopenharmony_ci u16 pid; 378c2ecf20Sopenharmony_ci}; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistruct cxd2880_pid_filter_config { 408c2ecf20Sopenharmony_ci u8 is_negative; 418c2ecf20Sopenharmony_ci struct cxd2880_pid_config pid_config[CXD2880_MAX_FILTER_SIZE]; 428c2ecf20Sopenharmony_ci}; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistruct cxd2880_dvb_spi { 458c2ecf20Sopenharmony_ci struct dvb_frontend dvb_fe; 468c2ecf20Sopenharmony_ci struct dvb_adapter adapter; 478c2ecf20Sopenharmony_ci struct dvb_demux demux; 488c2ecf20Sopenharmony_ci struct dmxdev dmxdev; 498c2ecf20Sopenharmony_ci struct dmx_frontend dmx_fe; 508c2ecf20Sopenharmony_ci struct task_struct *cxd2880_ts_read_thread; 518c2ecf20Sopenharmony_ci struct spi_device *spi; 528c2ecf20Sopenharmony_ci struct mutex spi_mutex; /* For SPI access exclusive control */ 538c2ecf20Sopenharmony_ci int feed_count; 548c2ecf20Sopenharmony_ci int all_pid_feed_count; 558c2ecf20Sopenharmony_ci struct regulator *vcc_supply; 568c2ecf20Sopenharmony_ci u8 *ts_buf; 578c2ecf20Sopenharmony_ci struct cxd2880_pid_filter_config filter_config; 588c2ecf20Sopenharmony_ci}; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ciDVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistatic int cxd2880_write_spi(struct spi_device *spi, u8 *data, u32 size) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci struct spi_message msg; 658c2ecf20Sopenharmony_ci struct spi_transfer tx = {}; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci if (!spi || !data) { 688c2ecf20Sopenharmony_ci pr_err("invalid arg\n"); 698c2ecf20Sopenharmony_ci return -EINVAL; 708c2ecf20Sopenharmony_ci } 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci tx.tx_buf = data; 738c2ecf20Sopenharmony_ci tx.len = size; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci spi_message_init(&msg); 768c2ecf20Sopenharmony_ci spi_message_add_tail(&tx, &msg); 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci return spi_sync(spi, &msg); 798c2ecf20Sopenharmony_ci} 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistatic int cxd2880_write_reg(struct spi_device *spi, 828c2ecf20Sopenharmony_ci u8 sub_address, const u8 *data, u32 size) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci u8 send_data[BURST_WRITE_MAX + 4]; 858c2ecf20Sopenharmony_ci const u8 *write_data_top = NULL; 868c2ecf20Sopenharmony_ci int ret = 0; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci if (!spi || !data) { 898c2ecf20Sopenharmony_ci pr_err("invalid arg\n"); 908c2ecf20Sopenharmony_ci return -EINVAL; 918c2ecf20Sopenharmony_ci } 928c2ecf20Sopenharmony_ci if (size > BURST_WRITE_MAX || size > U8_MAX) { 938c2ecf20Sopenharmony_ci pr_err("data size > WRITE_MAX\n"); 948c2ecf20Sopenharmony_ci return -EINVAL; 958c2ecf20Sopenharmony_ci } 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci if (sub_address + size > 0x100) { 988c2ecf20Sopenharmony_ci pr_err("out of range\n"); 998c2ecf20Sopenharmony_ci return -EINVAL; 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci send_data[0] = 0x0e; 1038c2ecf20Sopenharmony_ci write_data_top = data; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci send_data[1] = sub_address; 1068c2ecf20Sopenharmony_ci send_data[2] = (u8)size; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci memcpy(&send_data[3], write_data_top, send_data[2]); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci ret = cxd2880_write_spi(spi, send_data, send_data[2] + 3); 1118c2ecf20Sopenharmony_ci if (ret) 1128c2ecf20Sopenharmony_ci pr_err("write spi failed %d\n", ret); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci return ret; 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_cistatic int cxd2880_spi_read_ts(struct spi_device *spi, 1188c2ecf20Sopenharmony_ci u8 *read_data, 1198c2ecf20Sopenharmony_ci u32 packet_num) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci int ret; 1228c2ecf20Sopenharmony_ci u8 data[3]; 1238c2ecf20Sopenharmony_ci struct spi_message message; 1248c2ecf20Sopenharmony_ci struct spi_transfer transfer[2] = {}; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci if (!spi || !read_data || !packet_num) { 1278c2ecf20Sopenharmony_ci pr_err("invalid arg\n"); 1288c2ecf20Sopenharmony_ci return -EINVAL; 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci if (packet_num > 0xffff) { 1318c2ecf20Sopenharmony_ci pr_err("packet num > 0xffff\n"); 1328c2ecf20Sopenharmony_ci return -EINVAL; 1338c2ecf20Sopenharmony_ci } 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci data[0] = 0x10; 1368c2ecf20Sopenharmony_ci data[1] = packet_num >> 8; 1378c2ecf20Sopenharmony_ci data[2] = packet_num; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci spi_message_init(&message); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci transfer[0].len = 3; 1428c2ecf20Sopenharmony_ci transfer[0].tx_buf = data; 1438c2ecf20Sopenharmony_ci spi_message_add_tail(&transfer[0], &message); 1448c2ecf20Sopenharmony_ci transfer[1].len = packet_num * 188; 1458c2ecf20Sopenharmony_ci transfer[1].rx_buf = read_data; 1468c2ecf20Sopenharmony_ci spi_message_add_tail(&transfer[1], &message); 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci ret = spi_sync(spi, &message); 1498c2ecf20Sopenharmony_ci if (ret) 1508c2ecf20Sopenharmony_ci pr_err("spi_write_then_read failed\n"); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci return ret; 1538c2ecf20Sopenharmony_ci} 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_cistatic int cxd2880_spi_read_ts_buffer_info(struct spi_device *spi, 1568c2ecf20Sopenharmony_ci struct cxd2880_ts_buf_info *info) 1578c2ecf20Sopenharmony_ci{ 1588c2ecf20Sopenharmony_ci u8 send_data = 0x20; 1598c2ecf20Sopenharmony_ci u8 recv_data[2]; 1608c2ecf20Sopenharmony_ci int ret; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci if (!spi || !info) { 1638c2ecf20Sopenharmony_ci pr_err("invalid arg\n"); 1648c2ecf20Sopenharmony_ci return -EINVAL; 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci ret = spi_write_then_read(spi, &send_data, 1, 1688c2ecf20Sopenharmony_ci recv_data, sizeof(recv_data)); 1698c2ecf20Sopenharmony_ci if (ret) 1708c2ecf20Sopenharmony_ci pr_err("spi_write_then_read failed\n"); 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci info->read_ready = (recv_data[0] & 0x80) ? 1 : 0; 1738c2ecf20Sopenharmony_ci info->almost_full = (recv_data[0] & 0x40) ? 1 : 0; 1748c2ecf20Sopenharmony_ci info->almost_empty = (recv_data[0] & 0x20) ? 1 : 0; 1758c2ecf20Sopenharmony_ci info->overflow = (recv_data[0] & 0x10) ? 1 : 0; 1768c2ecf20Sopenharmony_ci info->underflow = (recv_data[0] & 0x08) ? 1 : 0; 1778c2ecf20Sopenharmony_ci info->pkt_num = ((recv_data[0] & 0x07) << 8) | recv_data[1]; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci return ret; 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_cistatic int cxd2880_spi_clear_ts_buffer(struct spi_device *spi) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci u8 data = 0x03; 1858c2ecf20Sopenharmony_ci int ret; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci ret = cxd2880_write_spi(spi, &data, 1); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci if (ret) 1908c2ecf20Sopenharmony_ci pr_err("write spi failed\n"); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci return ret; 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic int cxd2880_set_pid_filter(struct spi_device *spi, 1968c2ecf20Sopenharmony_ci struct cxd2880_pid_filter_config *cfg) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci u8 data[65]; 1998c2ecf20Sopenharmony_ci int i; 2008c2ecf20Sopenharmony_ci u16 pid = 0; 2018c2ecf20Sopenharmony_ci int ret; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci if (!spi) { 2048c2ecf20Sopenharmony_ci pr_err("invalid arg\n"); 2058c2ecf20Sopenharmony_ci return -EINVAL; 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci data[0] = 0x00; 2098c2ecf20Sopenharmony_ci ret = cxd2880_write_reg(spi, 0x00, &data[0], 1); 2108c2ecf20Sopenharmony_ci if (ret) 2118c2ecf20Sopenharmony_ci return ret; 2128c2ecf20Sopenharmony_ci if (!cfg) { 2138c2ecf20Sopenharmony_ci data[0] = 0x02; 2148c2ecf20Sopenharmony_ci ret = cxd2880_write_reg(spi, 0x50, &data[0], 1); 2158c2ecf20Sopenharmony_ci } else { 2168c2ecf20Sopenharmony_ci data[0] = cfg->is_negative ? 0x01 : 0x00; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci for (i = 0; i < CXD2880_MAX_FILTER_SIZE; i++) { 2198c2ecf20Sopenharmony_ci pid = cfg->pid_config[i].pid; 2208c2ecf20Sopenharmony_ci if (cfg->pid_config[i].is_enable) { 2218c2ecf20Sopenharmony_ci data[1 + (i * 2)] = (pid >> 8) | 0x20; 2228c2ecf20Sopenharmony_ci data[2 + (i * 2)] = pid & 0xff; 2238c2ecf20Sopenharmony_ci } else { 2248c2ecf20Sopenharmony_ci data[1 + (i * 2)] = 0x00; 2258c2ecf20Sopenharmony_ci data[2 + (i * 2)] = 0x00; 2268c2ecf20Sopenharmony_ci } 2278c2ecf20Sopenharmony_ci } 2288c2ecf20Sopenharmony_ci ret = cxd2880_write_reg(spi, 0x50, data, 65); 2298c2ecf20Sopenharmony_ci } 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci return ret; 2328c2ecf20Sopenharmony_ci} 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_cistatic int cxd2880_update_pid_filter(struct cxd2880_dvb_spi *dvb_spi, 2358c2ecf20Sopenharmony_ci struct cxd2880_pid_filter_config *cfg, 2368c2ecf20Sopenharmony_ci bool is_all_pid_filter) 2378c2ecf20Sopenharmony_ci{ 2388c2ecf20Sopenharmony_ci int ret; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci if (!dvb_spi || !cfg) { 2418c2ecf20Sopenharmony_ci pr_err("invalid arg.\n"); 2428c2ecf20Sopenharmony_ci return -EINVAL; 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci mutex_lock(&dvb_spi->spi_mutex); 2468c2ecf20Sopenharmony_ci if (is_all_pid_filter) { 2478c2ecf20Sopenharmony_ci struct cxd2880_pid_filter_config tmpcfg; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci memset(&tmpcfg, 0, sizeof(tmpcfg)); 2508c2ecf20Sopenharmony_ci tmpcfg.is_negative = 1; 2518c2ecf20Sopenharmony_ci tmpcfg.pid_config[0].is_enable = 1; 2528c2ecf20Sopenharmony_ci tmpcfg.pid_config[0].pid = 0x1fff; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci ret = cxd2880_set_pid_filter(dvb_spi->spi, &tmpcfg); 2558c2ecf20Sopenharmony_ci } else { 2568c2ecf20Sopenharmony_ci ret = cxd2880_set_pid_filter(dvb_spi->spi, cfg); 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci mutex_unlock(&dvb_spi->spi_mutex); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci if (ret) 2618c2ecf20Sopenharmony_ci pr_err("set_pid_filter failed\n"); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci return ret; 2648c2ecf20Sopenharmony_ci} 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_cistatic int cxd2880_ts_read(void *arg) 2678c2ecf20Sopenharmony_ci{ 2688c2ecf20Sopenharmony_ci struct cxd2880_dvb_spi *dvb_spi = NULL; 2698c2ecf20Sopenharmony_ci struct cxd2880_ts_buf_info info; 2708c2ecf20Sopenharmony_ci ktime_t start; 2718c2ecf20Sopenharmony_ci u32 i; 2728c2ecf20Sopenharmony_ci int ret; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci dvb_spi = arg; 2758c2ecf20Sopenharmony_ci if (!dvb_spi) { 2768c2ecf20Sopenharmony_ci pr_err("invalid arg\n"); 2778c2ecf20Sopenharmony_ci return -EINVAL; 2788c2ecf20Sopenharmony_ci } 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci ret = cxd2880_spi_clear_ts_buffer(dvb_spi->spi); 2818c2ecf20Sopenharmony_ci if (ret) { 2828c2ecf20Sopenharmony_ci pr_err("set_clear_ts_buffer failed\n"); 2838c2ecf20Sopenharmony_ci return ret; 2848c2ecf20Sopenharmony_ci } 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci start = ktime_get(); 2878c2ecf20Sopenharmony_ci while (!kthread_should_stop()) { 2888c2ecf20Sopenharmony_ci ret = cxd2880_spi_read_ts_buffer_info(dvb_spi->spi, 2898c2ecf20Sopenharmony_ci &info); 2908c2ecf20Sopenharmony_ci if (ret) { 2918c2ecf20Sopenharmony_ci pr_err("spi_read_ts_buffer_info error\n"); 2928c2ecf20Sopenharmony_ci return ret; 2938c2ecf20Sopenharmony_ci } 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci if (info.pkt_num > MAX_TRANS_PKT) { 2968c2ecf20Sopenharmony_ci for (i = 0; i < info.pkt_num / MAX_TRANS_PKT; i++) { 2978c2ecf20Sopenharmony_ci cxd2880_spi_read_ts(dvb_spi->spi, 2988c2ecf20Sopenharmony_ci dvb_spi->ts_buf, 2998c2ecf20Sopenharmony_ci MAX_TRANS_PKT); 3008c2ecf20Sopenharmony_ci dvb_dmx_swfilter(&dvb_spi->demux, 3018c2ecf20Sopenharmony_ci dvb_spi->ts_buf, 3028c2ecf20Sopenharmony_ci MAX_TRANS_PKT * 188); 3038c2ecf20Sopenharmony_ci } 3048c2ecf20Sopenharmony_ci start = ktime_get(); 3058c2ecf20Sopenharmony_ci } else if ((info.pkt_num > 0) && 3068c2ecf20Sopenharmony_ci (ktime_to_ms(ktime_sub(ktime_get(), start)) >= 500)) { 3078c2ecf20Sopenharmony_ci cxd2880_spi_read_ts(dvb_spi->spi, 3088c2ecf20Sopenharmony_ci dvb_spi->ts_buf, 3098c2ecf20Sopenharmony_ci info.pkt_num); 3108c2ecf20Sopenharmony_ci dvb_dmx_swfilter(&dvb_spi->demux, 3118c2ecf20Sopenharmony_ci dvb_spi->ts_buf, 3128c2ecf20Sopenharmony_ci info.pkt_num * 188); 3138c2ecf20Sopenharmony_ci start = ktime_get(); 3148c2ecf20Sopenharmony_ci } else { 3158c2ecf20Sopenharmony_ci usleep_range(10000, 11000); 3168c2ecf20Sopenharmony_ci } 3178c2ecf20Sopenharmony_ci } 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci return 0; 3208c2ecf20Sopenharmony_ci} 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_cistatic int cxd2880_start_feed(struct dvb_demux_feed *feed) 3238c2ecf20Sopenharmony_ci{ 3248c2ecf20Sopenharmony_ci int ret = 0; 3258c2ecf20Sopenharmony_ci int i = 0; 3268c2ecf20Sopenharmony_ci struct dvb_demux *demux = NULL; 3278c2ecf20Sopenharmony_ci struct cxd2880_dvb_spi *dvb_spi = NULL; 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci if (!feed) { 3308c2ecf20Sopenharmony_ci pr_err("invalid arg\n"); 3318c2ecf20Sopenharmony_ci return -EINVAL; 3328c2ecf20Sopenharmony_ci } 3338c2ecf20Sopenharmony_ci 3348c2ecf20Sopenharmony_ci demux = feed->demux; 3358c2ecf20Sopenharmony_ci if (!demux) { 3368c2ecf20Sopenharmony_ci pr_err("feed->demux is NULL\n"); 3378c2ecf20Sopenharmony_ci return -EINVAL; 3388c2ecf20Sopenharmony_ci } 3398c2ecf20Sopenharmony_ci dvb_spi = demux->priv; 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci if (dvb_spi->feed_count == CXD2880_MAX_FILTER_SIZE) { 3428c2ecf20Sopenharmony_ci pr_err("Exceeded maximum PID count (32)."); 3438c2ecf20Sopenharmony_ci pr_err("Selected PID cannot be enabled.\n"); 3448c2ecf20Sopenharmony_ci return -EINVAL; 3458c2ecf20Sopenharmony_ci } 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_ci if (feed->pid == 0x2000) { 3488c2ecf20Sopenharmony_ci if (dvb_spi->all_pid_feed_count == 0) { 3498c2ecf20Sopenharmony_ci ret = cxd2880_update_pid_filter(dvb_spi, 3508c2ecf20Sopenharmony_ci &dvb_spi->filter_config, 3518c2ecf20Sopenharmony_ci true); 3528c2ecf20Sopenharmony_ci if (ret) { 3538c2ecf20Sopenharmony_ci pr_err("update pid filter failed\n"); 3548c2ecf20Sopenharmony_ci return ret; 3558c2ecf20Sopenharmony_ci } 3568c2ecf20Sopenharmony_ci } 3578c2ecf20Sopenharmony_ci dvb_spi->all_pid_feed_count++; 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci pr_debug("all PID feed (count = %d)\n", 3608c2ecf20Sopenharmony_ci dvb_spi->all_pid_feed_count); 3618c2ecf20Sopenharmony_ci } else { 3628c2ecf20Sopenharmony_ci struct cxd2880_pid_filter_config cfgtmp; 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci cfgtmp = dvb_spi->filter_config; 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci for (i = 0; i < CXD2880_MAX_FILTER_SIZE; i++) { 3678c2ecf20Sopenharmony_ci if (cfgtmp.pid_config[i].is_enable == 0) { 3688c2ecf20Sopenharmony_ci cfgtmp.pid_config[i].is_enable = 1; 3698c2ecf20Sopenharmony_ci cfgtmp.pid_config[i].pid = feed->pid; 3708c2ecf20Sopenharmony_ci pr_debug("store PID %d to #%d\n", 3718c2ecf20Sopenharmony_ci feed->pid, i); 3728c2ecf20Sopenharmony_ci break; 3738c2ecf20Sopenharmony_ci } 3748c2ecf20Sopenharmony_ci } 3758c2ecf20Sopenharmony_ci if (i == CXD2880_MAX_FILTER_SIZE) { 3768c2ecf20Sopenharmony_ci pr_err("PID filter is full.\n"); 3778c2ecf20Sopenharmony_ci return -EINVAL; 3788c2ecf20Sopenharmony_ci } 3798c2ecf20Sopenharmony_ci if (!dvb_spi->all_pid_feed_count) 3808c2ecf20Sopenharmony_ci ret = cxd2880_update_pid_filter(dvb_spi, 3818c2ecf20Sopenharmony_ci &cfgtmp, 3828c2ecf20Sopenharmony_ci false); 3838c2ecf20Sopenharmony_ci if (ret) 3848c2ecf20Sopenharmony_ci return ret; 3858c2ecf20Sopenharmony_ci 3868c2ecf20Sopenharmony_ci dvb_spi->filter_config = cfgtmp; 3878c2ecf20Sopenharmony_ci } 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ci if (dvb_spi->feed_count == 0) { 3908c2ecf20Sopenharmony_ci dvb_spi->ts_buf = 3918c2ecf20Sopenharmony_ci kmalloc(MAX_TRANS_PKT * 188, 3928c2ecf20Sopenharmony_ci GFP_KERNEL | GFP_DMA); 3938c2ecf20Sopenharmony_ci if (!dvb_spi->ts_buf) { 3948c2ecf20Sopenharmony_ci pr_err("ts buffer allocate failed\n"); 3958c2ecf20Sopenharmony_ci memset(&dvb_spi->filter_config, 0, 3968c2ecf20Sopenharmony_ci sizeof(dvb_spi->filter_config)); 3978c2ecf20Sopenharmony_ci dvb_spi->all_pid_feed_count = 0; 3988c2ecf20Sopenharmony_ci return -ENOMEM; 3998c2ecf20Sopenharmony_ci } 4008c2ecf20Sopenharmony_ci dvb_spi->cxd2880_ts_read_thread = kthread_run(cxd2880_ts_read, 4018c2ecf20Sopenharmony_ci dvb_spi, 4028c2ecf20Sopenharmony_ci "cxd2880_ts_read"); 4038c2ecf20Sopenharmony_ci if (IS_ERR(dvb_spi->cxd2880_ts_read_thread)) { 4048c2ecf20Sopenharmony_ci pr_err("kthread_run failed/\n"); 4058c2ecf20Sopenharmony_ci kfree(dvb_spi->ts_buf); 4068c2ecf20Sopenharmony_ci dvb_spi->ts_buf = NULL; 4078c2ecf20Sopenharmony_ci memset(&dvb_spi->filter_config, 0, 4088c2ecf20Sopenharmony_ci sizeof(dvb_spi->filter_config)); 4098c2ecf20Sopenharmony_ci dvb_spi->all_pid_feed_count = 0; 4108c2ecf20Sopenharmony_ci return PTR_ERR(dvb_spi->cxd2880_ts_read_thread); 4118c2ecf20Sopenharmony_ci } 4128c2ecf20Sopenharmony_ci } 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_ci dvb_spi->feed_count++; 4158c2ecf20Sopenharmony_ci 4168c2ecf20Sopenharmony_ci pr_debug("start feed (count %d)\n", dvb_spi->feed_count); 4178c2ecf20Sopenharmony_ci return 0; 4188c2ecf20Sopenharmony_ci} 4198c2ecf20Sopenharmony_ci 4208c2ecf20Sopenharmony_cistatic int cxd2880_stop_feed(struct dvb_demux_feed *feed) 4218c2ecf20Sopenharmony_ci{ 4228c2ecf20Sopenharmony_ci int i = 0; 4238c2ecf20Sopenharmony_ci int ret; 4248c2ecf20Sopenharmony_ci struct dvb_demux *demux = NULL; 4258c2ecf20Sopenharmony_ci struct cxd2880_dvb_spi *dvb_spi = NULL; 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_ci if (!feed) { 4288c2ecf20Sopenharmony_ci pr_err("invalid arg\n"); 4298c2ecf20Sopenharmony_ci return -EINVAL; 4308c2ecf20Sopenharmony_ci } 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci demux = feed->demux; 4338c2ecf20Sopenharmony_ci if (!demux) { 4348c2ecf20Sopenharmony_ci pr_err("feed->demux is NULL\n"); 4358c2ecf20Sopenharmony_ci return -EINVAL; 4368c2ecf20Sopenharmony_ci } 4378c2ecf20Sopenharmony_ci dvb_spi = demux->priv; 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci if (!dvb_spi->feed_count) { 4408c2ecf20Sopenharmony_ci pr_err("no feed is started\n"); 4418c2ecf20Sopenharmony_ci return -EINVAL; 4428c2ecf20Sopenharmony_ci } 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_ci if (feed->pid == 0x2000) { 4458c2ecf20Sopenharmony_ci /* 4468c2ecf20Sopenharmony_ci * Special PID case. 4478c2ecf20Sopenharmony_ci * Number of 0x2000 feed request was stored 4488c2ecf20Sopenharmony_ci * in dvb_spi->all_pid_feed_count. 4498c2ecf20Sopenharmony_ci */ 4508c2ecf20Sopenharmony_ci if (dvb_spi->all_pid_feed_count <= 0) { 4518c2ecf20Sopenharmony_ci pr_err("PID %d not found.\n", feed->pid); 4528c2ecf20Sopenharmony_ci return -EINVAL; 4538c2ecf20Sopenharmony_ci } 4548c2ecf20Sopenharmony_ci dvb_spi->all_pid_feed_count--; 4558c2ecf20Sopenharmony_ci } else { 4568c2ecf20Sopenharmony_ci struct cxd2880_pid_filter_config cfgtmp; 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_ci cfgtmp = dvb_spi->filter_config; 4598c2ecf20Sopenharmony_ci 4608c2ecf20Sopenharmony_ci for (i = 0; i < CXD2880_MAX_FILTER_SIZE; i++) { 4618c2ecf20Sopenharmony_ci if (feed->pid == cfgtmp.pid_config[i].pid && 4628c2ecf20Sopenharmony_ci cfgtmp.pid_config[i].is_enable != 0) { 4638c2ecf20Sopenharmony_ci cfgtmp.pid_config[i].is_enable = 0; 4648c2ecf20Sopenharmony_ci cfgtmp.pid_config[i].pid = 0; 4658c2ecf20Sopenharmony_ci pr_debug("removed PID %d from #%d\n", 4668c2ecf20Sopenharmony_ci feed->pid, i); 4678c2ecf20Sopenharmony_ci break; 4688c2ecf20Sopenharmony_ci } 4698c2ecf20Sopenharmony_ci } 4708c2ecf20Sopenharmony_ci dvb_spi->filter_config = cfgtmp; 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_ci if (i == CXD2880_MAX_FILTER_SIZE) { 4738c2ecf20Sopenharmony_ci pr_err("PID %d not found\n", feed->pid); 4748c2ecf20Sopenharmony_ci return -EINVAL; 4758c2ecf20Sopenharmony_ci } 4768c2ecf20Sopenharmony_ci } 4778c2ecf20Sopenharmony_ci 4788c2ecf20Sopenharmony_ci ret = cxd2880_update_pid_filter(dvb_spi, 4798c2ecf20Sopenharmony_ci &dvb_spi->filter_config, 4808c2ecf20Sopenharmony_ci dvb_spi->all_pid_feed_count > 0); 4818c2ecf20Sopenharmony_ci dvb_spi->feed_count--; 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci if (dvb_spi->feed_count == 0) { 4848c2ecf20Sopenharmony_ci int ret_stop = 0; 4858c2ecf20Sopenharmony_ci 4868c2ecf20Sopenharmony_ci ret_stop = kthread_stop(dvb_spi->cxd2880_ts_read_thread); 4878c2ecf20Sopenharmony_ci if (ret_stop) { 4888c2ecf20Sopenharmony_ci pr_err("'kthread_stop failed. (%d)\n", ret_stop); 4898c2ecf20Sopenharmony_ci ret = ret_stop; 4908c2ecf20Sopenharmony_ci } 4918c2ecf20Sopenharmony_ci kfree(dvb_spi->ts_buf); 4928c2ecf20Sopenharmony_ci dvb_spi->ts_buf = NULL; 4938c2ecf20Sopenharmony_ci } 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_ci pr_debug("stop feed ok.(count %d)\n", dvb_spi->feed_count); 4968c2ecf20Sopenharmony_ci 4978c2ecf20Sopenharmony_ci return ret; 4988c2ecf20Sopenharmony_ci} 4998c2ecf20Sopenharmony_ci 5008c2ecf20Sopenharmony_cistatic const struct of_device_id cxd2880_spi_of_match[] = { 5018c2ecf20Sopenharmony_ci { .compatible = "sony,cxd2880" }, 5028c2ecf20Sopenharmony_ci { /* sentinel */ } 5038c2ecf20Sopenharmony_ci}; 5048c2ecf20Sopenharmony_ci 5058c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, cxd2880_spi_of_match); 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_cistatic int 5088c2ecf20Sopenharmony_cicxd2880_spi_probe(struct spi_device *spi) 5098c2ecf20Sopenharmony_ci{ 5108c2ecf20Sopenharmony_ci int ret; 5118c2ecf20Sopenharmony_ci struct cxd2880_dvb_spi *dvb_spi = NULL; 5128c2ecf20Sopenharmony_ci struct cxd2880_config config; 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci if (!spi) { 5158c2ecf20Sopenharmony_ci pr_err("invalid arg.\n"); 5168c2ecf20Sopenharmony_ci return -EINVAL; 5178c2ecf20Sopenharmony_ci } 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ci dvb_spi = kzalloc(sizeof(struct cxd2880_dvb_spi), GFP_KERNEL); 5208c2ecf20Sopenharmony_ci if (!dvb_spi) 5218c2ecf20Sopenharmony_ci return -ENOMEM; 5228c2ecf20Sopenharmony_ci 5238c2ecf20Sopenharmony_ci dvb_spi->vcc_supply = devm_regulator_get_optional(&spi->dev, "vcc"); 5248c2ecf20Sopenharmony_ci if (IS_ERR(dvb_spi->vcc_supply)) { 5258c2ecf20Sopenharmony_ci if (PTR_ERR(dvb_spi->vcc_supply) == -EPROBE_DEFER) { 5268c2ecf20Sopenharmony_ci ret = -EPROBE_DEFER; 5278c2ecf20Sopenharmony_ci goto fail_regulator; 5288c2ecf20Sopenharmony_ci } 5298c2ecf20Sopenharmony_ci dvb_spi->vcc_supply = NULL; 5308c2ecf20Sopenharmony_ci } else { 5318c2ecf20Sopenharmony_ci ret = regulator_enable(dvb_spi->vcc_supply); 5328c2ecf20Sopenharmony_ci if (ret) 5338c2ecf20Sopenharmony_ci goto fail_regulator; 5348c2ecf20Sopenharmony_ci } 5358c2ecf20Sopenharmony_ci 5368c2ecf20Sopenharmony_ci dvb_spi->spi = spi; 5378c2ecf20Sopenharmony_ci mutex_init(&dvb_spi->spi_mutex); 5388c2ecf20Sopenharmony_ci dev_set_drvdata(&spi->dev, dvb_spi); 5398c2ecf20Sopenharmony_ci config.spi = spi; 5408c2ecf20Sopenharmony_ci config.spi_mutex = &dvb_spi->spi_mutex; 5418c2ecf20Sopenharmony_ci 5428c2ecf20Sopenharmony_ci ret = dvb_register_adapter(&dvb_spi->adapter, 5438c2ecf20Sopenharmony_ci "CXD2880", 5448c2ecf20Sopenharmony_ci THIS_MODULE, 5458c2ecf20Sopenharmony_ci &spi->dev, 5468c2ecf20Sopenharmony_ci adapter_nr); 5478c2ecf20Sopenharmony_ci if (ret < 0) { 5488c2ecf20Sopenharmony_ci pr_err("dvb_register_adapter() failed\n"); 5498c2ecf20Sopenharmony_ci goto fail_adapter; 5508c2ecf20Sopenharmony_ci } 5518c2ecf20Sopenharmony_ci 5528c2ecf20Sopenharmony_ci if (!dvb_attach(cxd2880_attach, &dvb_spi->dvb_fe, &config)) { 5538c2ecf20Sopenharmony_ci pr_err("cxd2880_attach failed\n"); 5548c2ecf20Sopenharmony_ci ret = -ENODEV; 5558c2ecf20Sopenharmony_ci goto fail_attach; 5568c2ecf20Sopenharmony_ci } 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci ret = dvb_register_frontend(&dvb_spi->adapter, 5598c2ecf20Sopenharmony_ci &dvb_spi->dvb_fe); 5608c2ecf20Sopenharmony_ci if (ret < 0) { 5618c2ecf20Sopenharmony_ci pr_err("dvb_register_frontend() failed\n"); 5628c2ecf20Sopenharmony_ci goto fail_frontend; 5638c2ecf20Sopenharmony_ci } 5648c2ecf20Sopenharmony_ci 5658c2ecf20Sopenharmony_ci dvb_spi->demux.dmx.capabilities = DMX_TS_FILTERING; 5668c2ecf20Sopenharmony_ci dvb_spi->demux.priv = dvb_spi; 5678c2ecf20Sopenharmony_ci dvb_spi->demux.filternum = CXD2880_MAX_FILTER_SIZE; 5688c2ecf20Sopenharmony_ci dvb_spi->demux.feednum = CXD2880_MAX_FILTER_SIZE; 5698c2ecf20Sopenharmony_ci dvb_spi->demux.start_feed = cxd2880_start_feed; 5708c2ecf20Sopenharmony_ci dvb_spi->demux.stop_feed = cxd2880_stop_feed; 5718c2ecf20Sopenharmony_ci 5728c2ecf20Sopenharmony_ci ret = dvb_dmx_init(&dvb_spi->demux); 5738c2ecf20Sopenharmony_ci if (ret < 0) { 5748c2ecf20Sopenharmony_ci pr_err("dvb_dmx_init() failed\n"); 5758c2ecf20Sopenharmony_ci goto fail_dmx; 5768c2ecf20Sopenharmony_ci } 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci dvb_spi->dmxdev.filternum = CXD2880_MAX_FILTER_SIZE; 5798c2ecf20Sopenharmony_ci dvb_spi->dmxdev.demux = &dvb_spi->demux.dmx; 5808c2ecf20Sopenharmony_ci dvb_spi->dmxdev.capabilities = 0; 5818c2ecf20Sopenharmony_ci ret = dvb_dmxdev_init(&dvb_spi->dmxdev, 5828c2ecf20Sopenharmony_ci &dvb_spi->adapter); 5838c2ecf20Sopenharmony_ci if (ret < 0) { 5848c2ecf20Sopenharmony_ci pr_err("dvb_dmxdev_init() failed\n"); 5858c2ecf20Sopenharmony_ci goto fail_dmxdev; 5868c2ecf20Sopenharmony_ci } 5878c2ecf20Sopenharmony_ci 5888c2ecf20Sopenharmony_ci dvb_spi->dmx_fe.source = DMX_FRONTEND_0; 5898c2ecf20Sopenharmony_ci ret = dvb_spi->demux.dmx.add_frontend(&dvb_spi->demux.dmx, 5908c2ecf20Sopenharmony_ci &dvb_spi->dmx_fe); 5918c2ecf20Sopenharmony_ci if (ret < 0) { 5928c2ecf20Sopenharmony_ci pr_err("add_frontend() failed\n"); 5938c2ecf20Sopenharmony_ci goto fail_dmx_fe; 5948c2ecf20Sopenharmony_ci } 5958c2ecf20Sopenharmony_ci 5968c2ecf20Sopenharmony_ci ret = dvb_spi->demux.dmx.connect_frontend(&dvb_spi->demux.dmx, 5978c2ecf20Sopenharmony_ci &dvb_spi->dmx_fe); 5988c2ecf20Sopenharmony_ci if (ret < 0) { 5998c2ecf20Sopenharmony_ci pr_err("dvb_register_frontend() failed\n"); 6008c2ecf20Sopenharmony_ci goto fail_fe_conn; 6018c2ecf20Sopenharmony_ci } 6028c2ecf20Sopenharmony_ci 6038c2ecf20Sopenharmony_ci pr_info("Sony CXD2880 has successfully attached.\n"); 6048c2ecf20Sopenharmony_ci 6058c2ecf20Sopenharmony_ci return 0; 6068c2ecf20Sopenharmony_ci 6078c2ecf20Sopenharmony_cifail_fe_conn: 6088c2ecf20Sopenharmony_ci dvb_spi->demux.dmx.remove_frontend(&dvb_spi->demux.dmx, 6098c2ecf20Sopenharmony_ci &dvb_spi->dmx_fe); 6108c2ecf20Sopenharmony_cifail_dmx_fe: 6118c2ecf20Sopenharmony_ci dvb_dmxdev_release(&dvb_spi->dmxdev); 6128c2ecf20Sopenharmony_cifail_dmxdev: 6138c2ecf20Sopenharmony_ci dvb_dmx_release(&dvb_spi->demux); 6148c2ecf20Sopenharmony_cifail_dmx: 6158c2ecf20Sopenharmony_ci dvb_unregister_frontend(&dvb_spi->dvb_fe); 6168c2ecf20Sopenharmony_cifail_frontend: 6178c2ecf20Sopenharmony_ci dvb_frontend_detach(&dvb_spi->dvb_fe); 6188c2ecf20Sopenharmony_cifail_attach: 6198c2ecf20Sopenharmony_ci dvb_unregister_adapter(&dvb_spi->adapter); 6208c2ecf20Sopenharmony_cifail_adapter: 6218c2ecf20Sopenharmony_ci if (dvb_spi->vcc_supply) 6228c2ecf20Sopenharmony_ci regulator_disable(dvb_spi->vcc_supply); 6238c2ecf20Sopenharmony_cifail_regulator: 6248c2ecf20Sopenharmony_ci kfree(dvb_spi); 6258c2ecf20Sopenharmony_ci return ret; 6268c2ecf20Sopenharmony_ci} 6278c2ecf20Sopenharmony_ci 6288c2ecf20Sopenharmony_cistatic int 6298c2ecf20Sopenharmony_cicxd2880_spi_remove(struct spi_device *spi) 6308c2ecf20Sopenharmony_ci{ 6318c2ecf20Sopenharmony_ci struct cxd2880_dvb_spi *dvb_spi; 6328c2ecf20Sopenharmony_ci 6338c2ecf20Sopenharmony_ci if (!spi) { 6348c2ecf20Sopenharmony_ci pr_err("invalid arg\n"); 6358c2ecf20Sopenharmony_ci return -EINVAL; 6368c2ecf20Sopenharmony_ci } 6378c2ecf20Sopenharmony_ci 6388c2ecf20Sopenharmony_ci dvb_spi = dev_get_drvdata(&spi->dev); 6398c2ecf20Sopenharmony_ci 6408c2ecf20Sopenharmony_ci if (!dvb_spi) { 6418c2ecf20Sopenharmony_ci pr_err("failed\n"); 6428c2ecf20Sopenharmony_ci return -EINVAL; 6438c2ecf20Sopenharmony_ci } 6448c2ecf20Sopenharmony_ci dvb_spi->demux.dmx.remove_frontend(&dvb_spi->demux.dmx, 6458c2ecf20Sopenharmony_ci &dvb_spi->dmx_fe); 6468c2ecf20Sopenharmony_ci dvb_dmxdev_release(&dvb_spi->dmxdev); 6478c2ecf20Sopenharmony_ci dvb_dmx_release(&dvb_spi->demux); 6488c2ecf20Sopenharmony_ci dvb_unregister_frontend(&dvb_spi->dvb_fe); 6498c2ecf20Sopenharmony_ci dvb_frontend_detach(&dvb_spi->dvb_fe); 6508c2ecf20Sopenharmony_ci dvb_unregister_adapter(&dvb_spi->adapter); 6518c2ecf20Sopenharmony_ci 6528c2ecf20Sopenharmony_ci if (dvb_spi->vcc_supply) 6538c2ecf20Sopenharmony_ci regulator_disable(dvb_spi->vcc_supply); 6548c2ecf20Sopenharmony_ci 6558c2ecf20Sopenharmony_ci kfree(dvb_spi); 6568c2ecf20Sopenharmony_ci pr_info("cxd2880_spi remove ok.\n"); 6578c2ecf20Sopenharmony_ci 6588c2ecf20Sopenharmony_ci return 0; 6598c2ecf20Sopenharmony_ci} 6608c2ecf20Sopenharmony_ci 6618c2ecf20Sopenharmony_cistatic const struct spi_device_id cxd2880_spi_id[] = { 6628c2ecf20Sopenharmony_ci { "cxd2880", 0 }, 6638c2ecf20Sopenharmony_ci { /* sentinel */ } 6648c2ecf20Sopenharmony_ci}; 6658c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(spi, cxd2880_spi_id); 6668c2ecf20Sopenharmony_ci 6678c2ecf20Sopenharmony_cistatic struct spi_driver cxd2880_spi_driver = { 6688c2ecf20Sopenharmony_ci .driver = { 6698c2ecf20Sopenharmony_ci .name = "cxd2880", 6708c2ecf20Sopenharmony_ci .of_match_table = cxd2880_spi_of_match, 6718c2ecf20Sopenharmony_ci }, 6728c2ecf20Sopenharmony_ci .id_table = cxd2880_spi_id, 6738c2ecf20Sopenharmony_ci .probe = cxd2880_spi_probe, 6748c2ecf20Sopenharmony_ci .remove = cxd2880_spi_remove, 6758c2ecf20Sopenharmony_ci}; 6768c2ecf20Sopenharmony_cimodule_spi_driver(cxd2880_spi_driver); 6778c2ecf20Sopenharmony_ci 6788c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Sony CXD2880 DVB-T2/T tuner + demod driver SPI adapter"); 6798c2ecf20Sopenharmony_ciMODULE_AUTHOR("Sony Semiconductor Solutions Corporation"); 6808c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 681