162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Stereo and SAP detection for cx88 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2009 Marton Balint <cus@fazekas.hu> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include "cx88.h" 962306a36Sopenharmony_ci#include "cx88-reg.h" 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/slab.h> 1262306a36Sopenharmony_ci#include <linux/kernel.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/jiffies.h> 1562306a36Sopenharmony_ci#include <asm/div64.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define INT_PI ((s32)(3.141592653589 * 32768.0)) 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#define compat_remainder(a, b) \ 2062306a36Sopenharmony_ci ((float)(((s32)((a) * 100)) % ((s32)((b) * 100))) / 100.0) 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define baseband_freq(carrier, srate, tone) ((s32)( \ 2362306a36Sopenharmony_ci (compat_remainder(carrier + tone, srate)) / srate * 2 * INT_PI)) 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* 2662306a36Sopenharmony_ci * We calculate the baseband frequencies of the carrier and the pilot tones 2762306a36Sopenharmony_ci * based on the sampling rate of the audio rds fifo. 2862306a36Sopenharmony_ci */ 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#define FREQ_A2_CARRIER baseband_freq(54687.5, 2689.36, 0.0) 3162306a36Sopenharmony_ci#define FREQ_A2_DUAL baseband_freq(54687.5, 2689.36, 274.1) 3262306a36Sopenharmony_ci#define FREQ_A2_STEREO baseband_freq(54687.5, 2689.36, 117.5) 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci/* 3562306a36Sopenharmony_ci * The frequencies below are from the reference driver. They probably need 3662306a36Sopenharmony_ci * further adjustments, because they are not tested at all. You may even need 3762306a36Sopenharmony_ci * to play a bit with the registers of the chip to select the proper signal 3862306a36Sopenharmony_ci * for the input of the audio rds fifo, and measure it's sampling rate to 3962306a36Sopenharmony_ci * calculate the proper baseband frequencies... 4062306a36Sopenharmony_ci */ 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define FREQ_A2M_CARRIER ((s32)(2.114516 * 32768.0)) 4362306a36Sopenharmony_ci#define FREQ_A2M_DUAL ((s32)(2.754916 * 32768.0)) 4462306a36Sopenharmony_ci#define FREQ_A2M_STEREO ((s32)(2.462326 * 32768.0)) 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci#define FREQ_EIAJ_CARRIER ((s32)(1.963495 * 32768.0)) /* 5pi/8 */ 4762306a36Sopenharmony_ci#define FREQ_EIAJ_DUAL ((s32)(2.562118 * 32768.0)) 4862306a36Sopenharmony_ci#define FREQ_EIAJ_STEREO ((s32)(2.601053 * 32768.0)) 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci#define FREQ_BTSC_DUAL ((s32)(1.963495 * 32768.0)) /* 5pi/8 */ 5162306a36Sopenharmony_ci#define FREQ_BTSC_DUAL_REF ((s32)(1.374446 * 32768.0)) /* 7pi/16 */ 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci#define FREQ_BTSC_SAP ((s32)(2.471532 * 32768.0)) 5462306a36Sopenharmony_ci#define FREQ_BTSC_SAP_REF ((s32)(1.730072 * 32768.0)) 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci/* The spectrum of the signal should be empty between these frequencies. */ 5762306a36Sopenharmony_ci#define FREQ_NOISE_START ((s32)(0.100000 * 32768.0)) 5862306a36Sopenharmony_ci#define FREQ_NOISE_END ((s32)(1.200000 * 32768.0)) 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic unsigned int dsp_debug; 6162306a36Sopenharmony_cimodule_param(dsp_debug, int, 0644); 6262306a36Sopenharmony_ciMODULE_PARM_DESC(dsp_debug, "enable audio dsp debug messages"); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci#define dprintk(level, fmt, arg...) do { \ 6562306a36Sopenharmony_ci if (dsp_debug >= level) \ 6662306a36Sopenharmony_ci printk(KERN_DEBUG pr_fmt("%s: dsp:" fmt), \ 6762306a36Sopenharmony_ci __func__, ##arg); \ 6862306a36Sopenharmony_ci} while (0) 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic s32 int_cos(u32 x) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci u32 t2, t4, t6, t8; 7362306a36Sopenharmony_ci s32 ret; 7462306a36Sopenharmony_ci u16 period = x / INT_PI; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci if (period % 2) 7762306a36Sopenharmony_ci return -int_cos(x - INT_PI); 7862306a36Sopenharmony_ci x = x % INT_PI; 7962306a36Sopenharmony_ci if (x > INT_PI / 2) 8062306a36Sopenharmony_ci return -int_cos(INT_PI / 2 - (x % (INT_PI / 2))); 8162306a36Sopenharmony_ci /* 8262306a36Sopenharmony_ci * Now x is between 0 and INT_PI/2. 8362306a36Sopenharmony_ci * To calculate cos(x) we use it's Taylor polinom. 8462306a36Sopenharmony_ci */ 8562306a36Sopenharmony_ci t2 = x * x / 32768 / 2; 8662306a36Sopenharmony_ci t4 = t2 * x / 32768 * x / 32768 / 3 / 4; 8762306a36Sopenharmony_ci t6 = t4 * x / 32768 * x / 32768 / 5 / 6; 8862306a36Sopenharmony_ci t8 = t6 * x / 32768 * x / 32768 / 7 / 8; 8962306a36Sopenharmony_ci ret = 32768 - t2 + t4 - t6 + t8; 9062306a36Sopenharmony_ci return ret; 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistatic u32 int_goertzel(s16 x[], u32 N, u32 freq) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci /* 9662306a36Sopenharmony_ci * We use the Goertzel algorithm to determine the power of the 9762306a36Sopenharmony_ci * given frequency in the signal 9862306a36Sopenharmony_ci */ 9962306a36Sopenharmony_ci s32 s_prev = 0; 10062306a36Sopenharmony_ci s32 s_prev2 = 0; 10162306a36Sopenharmony_ci s32 coeff = 2 * int_cos(freq); 10262306a36Sopenharmony_ci u32 i; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci u64 tmp; 10562306a36Sopenharmony_ci u32 divisor; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci for (i = 0; i < N; i++) { 10862306a36Sopenharmony_ci s32 s = x[i] + ((s64)coeff * s_prev / 32768) - s_prev2; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci s_prev2 = s_prev; 11162306a36Sopenharmony_ci s_prev = s; 11262306a36Sopenharmony_ci } 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci tmp = (s64)s_prev2 * s_prev2 + (s64)s_prev * s_prev - 11562306a36Sopenharmony_ci (s64)coeff * s_prev2 * s_prev / 32768; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci /* 11862306a36Sopenharmony_ci * XXX: N must be low enough so that N*N fits in s32. 11962306a36Sopenharmony_ci * Else we need two divisions. 12062306a36Sopenharmony_ci */ 12162306a36Sopenharmony_ci divisor = N * N; 12262306a36Sopenharmony_ci do_div(tmp, divisor); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci return (u32)tmp; 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cistatic u32 freq_magnitude(s16 x[], u32 N, u32 freq) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci u32 sum = int_goertzel(x, N, freq); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci return (u32)int_sqrt(sum); 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic u32 noise_magnitude(s16 x[], u32 N, u32 freq_start, u32 freq_end) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci int i; 13762306a36Sopenharmony_ci u32 sum = 0; 13862306a36Sopenharmony_ci u32 freq_step; 13962306a36Sopenharmony_ci int samples = 5; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci if (N > 192) { 14262306a36Sopenharmony_ci /* The last 192 samples are enough for noise detection */ 14362306a36Sopenharmony_ci x += (N - 192); 14462306a36Sopenharmony_ci N = 192; 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci freq_step = (freq_end - freq_start) / (samples - 1); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci for (i = 0; i < samples; i++) { 15062306a36Sopenharmony_ci sum += int_goertzel(x, N, freq_start); 15162306a36Sopenharmony_ci freq_start += freq_step; 15262306a36Sopenharmony_ci } 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci return (u32)int_sqrt(sum / samples); 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cistatic s32 detect_a2_a2m_eiaj(struct cx88_core *core, s16 x[], u32 N) 15862306a36Sopenharmony_ci{ 15962306a36Sopenharmony_ci s32 carrier, stereo, dual, noise; 16062306a36Sopenharmony_ci s32 carrier_freq, stereo_freq, dual_freq; 16162306a36Sopenharmony_ci s32 ret; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci switch (core->tvaudio) { 16462306a36Sopenharmony_ci case WW_BG: 16562306a36Sopenharmony_ci case WW_DK: 16662306a36Sopenharmony_ci carrier_freq = FREQ_A2_CARRIER; 16762306a36Sopenharmony_ci stereo_freq = FREQ_A2_STEREO; 16862306a36Sopenharmony_ci dual_freq = FREQ_A2_DUAL; 16962306a36Sopenharmony_ci break; 17062306a36Sopenharmony_ci case WW_M: 17162306a36Sopenharmony_ci carrier_freq = FREQ_A2M_CARRIER; 17262306a36Sopenharmony_ci stereo_freq = FREQ_A2M_STEREO; 17362306a36Sopenharmony_ci dual_freq = FREQ_A2M_DUAL; 17462306a36Sopenharmony_ci break; 17562306a36Sopenharmony_ci case WW_EIAJ: 17662306a36Sopenharmony_ci carrier_freq = FREQ_EIAJ_CARRIER; 17762306a36Sopenharmony_ci stereo_freq = FREQ_EIAJ_STEREO; 17862306a36Sopenharmony_ci dual_freq = FREQ_EIAJ_DUAL; 17962306a36Sopenharmony_ci break; 18062306a36Sopenharmony_ci default: 18162306a36Sopenharmony_ci pr_warn("unsupported audio mode %d for %s\n", 18262306a36Sopenharmony_ci core->tvaudio, __func__); 18362306a36Sopenharmony_ci return UNSET; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci carrier = freq_magnitude(x, N, carrier_freq); 18762306a36Sopenharmony_ci stereo = freq_magnitude(x, N, stereo_freq); 18862306a36Sopenharmony_ci dual = freq_magnitude(x, N, dual_freq); 18962306a36Sopenharmony_ci noise = noise_magnitude(x, N, FREQ_NOISE_START, FREQ_NOISE_END); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci dprintk(1, 19262306a36Sopenharmony_ci "detect a2/a2m/eiaj: carrier=%d, stereo=%d, dual=%d, noise=%d\n", 19362306a36Sopenharmony_ci carrier, stereo, dual, noise); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci if (stereo > dual) 19662306a36Sopenharmony_ci ret = V4L2_TUNER_SUB_STEREO; 19762306a36Sopenharmony_ci else 19862306a36Sopenharmony_ci ret = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci if (core->tvaudio == WW_EIAJ) { 20162306a36Sopenharmony_ci /* EIAJ checks may need adjustments */ 20262306a36Sopenharmony_ci if ((carrier > max(stereo, dual) * 2) && 20362306a36Sopenharmony_ci (carrier < max(stereo, dual) * 6) && 20462306a36Sopenharmony_ci (carrier > 20 && carrier < 200) && 20562306a36Sopenharmony_ci (max(stereo, dual) > min(stereo, dual))) { 20662306a36Sopenharmony_ci /* 20762306a36Sopenharmony_ci * For EIAJ the carrier is always present, 20862306a36Sopenharmony_ci * so we probably don't need noise detection 20962306a36Sopenharmony_ci */ 21062306a36Sopenharmony_ci return ret; 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci } else { 21362306a36Sopenharmony_ci if ((carrier > max(stereo, dual) * 2) && 21462306a36Sopenharmony_ci (carrier < max(stereo, dual) * 8) && 21562306a36Sopenharmony_ci (carrier > 20 && carrier < 200) && 21662306a36Sopenharmony_ci (noise < 10) && 21762306a36Sopenharmony_ci (max(stereo, dual) > min(stereo, dual) * 2)) { 21862306a36Sopenharmony_ci return ret; 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci } 22162306a36Sopenharmony_ci return V4L2_TUNER_SUB_MONO; 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cistatic s32 detect_btsc(struct cx88_core *core, s16 x[], u32 N) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci s32 sap_ref = freq_magnitude(x, N, FREQ_BTSC_SAP_REF); 22762306a36Sopenharmony_ci s32 sap = freq_magnitude(x, N, FREQ_BTSC_SAP); 22862306a36Sopenharmony_ci s32 dual_ref = freq_magnitude(x, N, FREQ_BTSC_DUAL_REF); 22962306a36Sopenharmony_ci s32 dual = freq_magnitude(x, N, FREQ_BTSC_DUAL); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci dprintk(1, "detect btsc: dual_ref=%d, dual=%d, sap_ref=%d, sap=%d\n", 23262306a36Sopenharmony_ci dual_ref, dual, sap_ref, sap); 23362306a36Sopenharmony_ci /* FIXME: Currently not supported */ 23462306a36Sopenharmony_ci return UNSET; 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cistatic s16 *read_rds_samples(struct cx88_core *core, u32 *N) 23862306a36Sopenharmony_ci{ 23962306a36Sopenharmony_ci const struct sram_channel *srch = &cx88_sram_channels[SRAM_CH27]; 24062306a36Sopenharmony_ci s16 *samples; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci unsigned int i; 24362306a36Sopenharmony_ci unsigned int bpl = srch->fifo_size / AUD_RDS_LINES; 24462306a36Sopenharmony_ci unsigned int spl = bpl / 4; 24562306a36Sopenharmony_ci unsigned int sample_count = spl * (AUD_RDS_LINES - 1); 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci u32 current_address = cx_read(srch->ptr1_reg); 24862306a36Sopenharmony_ci u32 offset = (current_address - srch->fifo_start + bpl); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci dprintk(1, 25162306a36Sopenharmony_ci "read RDS samples: current_address=%08x (offset=%08x), sample_count=%d, aud_intstat=%08x\n", 25262306a36Sopenharmony_ci current_address, 25362306a36Sopenharmony_ci current_address - srch->fifo_start, sample_count, 25462306a36Sopenharmony_ci cx_read(MO_AUD_INTSTAT)); 25562306a36Sopenharmony_ci samples = kmalloc_array(sample_count, sizeof(*samples), GFP_KERNEL); 25662306a36Sopenharmony_ci if (!samples) 25762306a36Sopenharmony_ci return NULL; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci *N = sample_count; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci for (i = 0; i < sample_count; i++) { 26262306a36Sopenharmony_ci offset = offset % (AUD_RDS_LINES * bpl); 26362306a36Sopenharmony_ci samples[i] = cx_read(srch->fifo_start + offset); 26462306a36Sopenharmony_ci offset += 4; 26562306a36Sopenharmony_ci } 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci dprintk(2, "RDS samples dump: %*ph\n", sample_count, samples); 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci return samples; 27062306a36Sopenharmony_ci} 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cis32 cx88_dsp_detect_stereo_sap(struct cx88_core *core) 27362306a36Sopenharmony_ci{ 27462306a36Sopenharmony_ci s16 *samples; 27562306a36Sopenharmony_ci u32 N = 0; 27662306a36Sopenharmony_ci s32 ret = UNSET; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci /* If audio RDS fifo is disabled, we can't read the samples */ 27962306a36Sopenharmony_ci if (!(cx_read(MO_AUD_DMACNTRL) & 0x04)) 28062306a36Sopenharmony_ci return ret; 28162306a36Sopenharmony_ci if (!(cx_read(AUD_CTL) & EN_FMRADIO_EN_RDS)) 28262306a36Sopenharmony_ci return ret; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci /* Wait at least 500 ms after an audio standard change */ 28562306a36Sopenharmony_ci if (time_before(jiffies, core->last_change + msecs_to_jiffies(500))) 28662306a36Sopenharmony_ci return ret; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci samples = read_rds_samples(core, &N); 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci if (!samples) 29162306a36Sopenharmony_ci return ret; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci switch (core->tvaudio) { 29462306a36Sopenharmony_ci case WW_BG: 29562306a36Sopenharmony_ci case WW_DK: 29662306a36Sopenharmony_ci case WW_EIAJ: 29762306a36Sopenharmony_ci case WW_M: 29862306a36Sopenharmony_ci ret = detect_a2_a2m_eiaj(core, samples, N); 29962306a36Sopenharmony_ci break; 30062306a36Sopenharmony_ci case WW_BTSC: 30162306a36Sopenharmony_ci ret = detect_btsc(core, samples, N); 30262306a36Sopenharmony_ci break; 30362306a36Sopenharmony_ci case WW_NONE: 30462306a36Sopenharmony_ci case WW_I: 30562306a36Sopenharmony_ci case WW_L: 30662306a36Sopenharmony_ci case WW_I2SPT: 30762306a36Sopenharmony_ci case WW_FM: 30862306a36Sopenharmony_ci case WW_I2SADC: 30962306a36Sopenharmony_ci break; 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci kfree(samples); 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci if (ret != UNSET) 31562306a36Sopenharmony_ci dprintk(1, "stereo/sap detection result:%s%s%s\n", 31662306a36Sopenharmony_ci (ret & V4L2_TUNER_SUB_MONO) ? " mono" : "", 31762306a36Sopenharmony_ci (ret & V4L2_TUNER_SUB_STEREO) ? " stereo" : "", 31862306a36Sopenharmony_ci (ret & V4L2_TUNER_SUB_LANG2) ? " dual" : ""); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci return ret; 32162306a36Sopenharmony_ci} 32262306a36Sopenharmony_ciEXPORT_SYMBOL(cx88_dsp_detect_stereo_sap); 32362306a36Sopenharmony_ci 324