162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * vivid-rds-gen.c - rds (radio data system) generator support functions. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/kernel.h> 962306a36Sopenharmony_ci#include <linux/ktime.h> 1062306a36Sopenharmony_ci#include <linux/string.h> 1162306a36Sopenharmony_ci#include <linux/videodev2.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include "vivid-rds-gen.h" 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_cistatic u8 vivid_get_di(const struct vivid_rds_gen *rds, unsigned grp) 1662306a36Sopenharmony_ci{ 1762306a36Sopenharmony_ci switch (grp) { 1862306a36Sopenharmony_ci case 0: 1962306a36Sopenharmony_ci return (rds->dyn_pty << 2) | (grp & 3); 2062306a36Sopenharmony_ci case 1: 2162306a36Sopenharmony_ci return (rds->compressed << 2) | (grp & 3); 2262306a36Sopenharmony_ci case 2: 2362306a36Sopenharmony_ci return (rds->art_head << 2) | (grp & 3); 2462306a36Sopenharmony_ci case 3: 2562306a36Sopenharmony_ci return (rds->mono_stereo << 2) | (grp & 3); 2662306a36Sopenharmony_ci } 2762306a36Sopenharmony_ci return 0; 2862306a36Sopenharmony_ci} 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci/* 3162306a36Sopenharmony_ci * This RDS generator creates 57 RDS groups (one group == four RDS blocks). 3262306a36Sopenharmony_ci * Groups 0-3, 22-25 and 44-47 (spaced 22 groups apart) are filled with a 3362306a36Sopenharmony_ci * standard 0B group containing the PI code and PS name. 3462306a36Sopenharmony_ci * 3562306a36Sopenharmony_ci * Groups 4-19 and 26-41 use group 2A for the radio text. 3662306a36Sopenharmony_ci * 3762306a36Sopenharmony_ci * Group 56 contains the time (group 4A). 3862306a36Sopenharmony_ci * 3962306a36Sopenharmony_ci * All remaining groups use a filler group 15B block that just repeats 4062306a36Sopenharmony_ci * the PI and PTY codes. 4162306a36Sopenharmony_ci */ 4262306a36Sopenharmony_civoid vivid_rds_generate(struct vivid_rds_gen *rds) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci struct v4l2_rds_data *data = rds->data; 4562306a36Sopenharmony_ci unsigned grp; 4662306a36Sopenharmony_ci unsigned idx; 4762306a36Sopenharmony_ci struct tm tm; 4862306a36Sopenharmony_ci unsigned date; 4962306a36Sopenharmony_ci unsigned time; 5062306a36Sopenharmony_ci int l; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci for (grp = 0; grp < VIVID_RDS_GEN_GROUPS; grp++, data += VIVID_RDS_GEN_BLKS_PER_GRP) { 5362306a36Sopenharmony_ci data[0].lsb = rds->picode & 0xff; 5462306a36Sopenharmony_ci data[0].msb = rds->picode >> 8; 5562306a36Sopenharmony_ci data[0].block = V4L2_RDS_BLOCK_A | (V4L2_RDS_BLOCK_A << 3); 5662306a36Sopenharmony_ci data[1].lsb = rds->pty << 5; 5762306a36Sopenharmony_ci data[1].msb = (rds->pty >> 3) | (rds->tp << 2); 5862306a36Sopenharmony_ci data[1].block = V4L2_RDS_BLOCK_B | (V4L2_RDS_BLOCK_B << 3); 5962306a36Sopenharmony_ci data[3].block = V4L2_RDS_BLOCK_D | (V4L2_RDS_BLOCK_D << 3); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci switch (grp) { 6262306a36Sopenharmony_ci case 0 ... 3: 6362306a36Sopenharmony_ci case 22 ... 25: 6462306a36Sopenharmony_ci case 44 ... 47: /* Group 0B */ 6562306a36Sopenharmony_ci idx = (grp % 22) % 4; 6662306a36Sopenharmony_ci data[1].lsb |= (rds->ta << 4) | (rds->ms << 3); 6762306a36Sopenharmony_ci data[1].lsb |= vivid_get_di(rds, idx); 6862306a36Sopenharmony_ci data[1].msb |= 1 << 3; 6962306a36Sopenharmony_ci data[2].lsb = rds->picode & 0xff; 7062306a36Sopenharmony_ci data[2].msb = rds->picode >> 8; 7162306a36Sopenharmony_ci data[2].block = V4L2_RDS_BLOCK_C_ALT | (V4L2_RDS_BLOCK_C_ALT << 3); 7262306a36Sopenharmony_ci data[3].lsb = rds->psname[2 * idx + 1]; 7362306a36Sopenharmony_ci data[3].msb = rds->psname[2 * idx]; 7462306a36Sopenharmony_ci break; 7562306a36Sopenharmony_ci case 4 ... 19: 7662306a36Sopenharmony_ci case 26 ... 41: /* Group 2A */ 7762306a36Sopenharmony_ci idx = ((grp - 4) % 22) % 16; 7862306a36Sopenharmony_ci data[1].lsb |= idx; 7962306a36Sopenharmony_ci data[1].msb |= 4 << 3; 8062306a36Sopenharmony_ci data[2].msb = rds->radiotext[4 * idx]; 8162306a36Sopenharmony_ci data[2].lsb = rds->radiotext[4 * idx + 1]; 8262306a36Sopenharmony_ci data[2].block = V4L2_RDS_BLOCK_C | (V4L2_RDS_BLOCK_C << 3); 8362306a36Sopenharmony_ci data[3].msb = rds->radiotext[4 * idx + 2]; 8462306a36Sopenharmony_ci data[3].lsb = rds->radiotext[4 * idx + 3]; 8562306a36Sopenharmony_ci break; 8662306a36Sopenharmony_ci case 56: 8762306a36Sopenharmony_ci /* 8862306a36Sopenharmony_ci * Group 4A 8962306a36Sopenharmony_ci * 9062306a36Sopenharmony_ci * Uses the algorithm from Annex G of the RDS standard 9162306a36Sopenharmony_ci * EN 50067:1998 to convert a UTC date to an RDS Modified 9262306a36Sopenharmony_ci * Julian Day. 9362306a36Sopenharmony_ci */ 9462306a36Sopenharmony_ci time64_to_tm(ktime_get_real_seconds(), 0, &tm); 9562306a36Sopenharmony_ci l = tm.tm_mon <= 1; 9662306a36Sopenharmony_ci date = 14956 + tm.tm_mday + ((tm.tm_year - l) * 1461) / 4 + 9762306a36Sopenharmony_ci ((tm.tm_mon + 2 + l * 12) * 306001) / 10000; 9862306a36Sopenharmony_ci time = (tm.tm_hour << 12) | 9962306a36Sopenharmony_ci (tm.tm_min << 6) | 10062306a36Sopenharmony_ci (sys_tz.tz_minuteswest >= 0 ? 0x20 : 0) | 10162306a36Sopenharmony_ci (abs(sys_tz.tz_minuteswest) / 30); 10262306a36Sopenharmony_ci data[1].lsb &= ~3; 10362306a36Sopenharmony_ci data[1].lsb |= date >> 15; 10462306a36Sopenharmony_ci data[1].msb |= 8 << 3; 10562306a36Sopenharmony_ci data[2].lsb = (date << 1) & 0xfe; 10662306a36Sopenharmony_ci data[2].lsb |= (time >> 16) & 1; 10762306a36Sopenharmony_ci data[2].msb = (date >> 7) & 0xff; 10862306a36Sopenharmony_ci data[2].block = V4L2_RDS_BLOCK_C | (V4L2_RDS_BLOCK_C << 3); 10962306a36Sopenharmony_ci data[3].lsb = time & 0xff; 11062306a36Sopenharmony_ci data[3].msb = (time >> 8) & 0xff; 11162306a36Sopenharmony_ci break; 11262306a36Sopenharmony_ci default: /* Group 15B */ 11362306a36Sopenharmony_ci data[1].lsb |= (rds->ta << 4) | (rds->ms << 3); 11462306a36Sopenharmony_ci data[1].lsb |= vivid_get_di(rds, grp % 22); 11562306a36Sopenharmony_ci data[1].msb |= 0x1f << 3; 11662306a36Sopenharmony_ci data[2].lsb = rds->picode & 0xff; 11762306a36Sopenharmony_ci data[2].msb = rds->picode >> 8; 11862306a36Sopenharmony_ci data[2].block = V4L2_RDS_BLOCK_C_ALT | (V4L2_RDS_BLOCK_C_ALT << 3); 11962306a36Sopenharmony_ci data[3].lsb = rds->pty << 5; 12062306a36Sopenharmony_ci data[3].lsb |= (rds->ta << 4) | (rds->ms << 3); 12162306a36Sopenharmony_ci data[3].lsb |= vivid_get_di(rds, grp % 22); 12262306a36Sopenharmony_ci data[3].msb |= rds->pty >> 3; 12362306a36Sopenharmony_ci data[3].msb |= 0x1f << 3; 12462306a36Sopenharmony_ci break; 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_civoid vivid_rds_gen_fill(struct vivid_rds_gen *rds, unsigned freq, 13062306a36Sopenharmony_ci bool alt) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci /* Alternate PTY between Info and Weather */ 13362306a36Sopenharmony_ci if (rds->use_rbds) { 13462306a36Sopenharmony_ci rds->picode = 0x2e75; /* 'KLNX' call sign */ 13562306a36Sopenharmony_ci rds->pty = alt ? 29 : 2; 13662306a36Sopenharmony_ci } else { 13762306a36Sopenharmony_ci rds->picode = 0x8088; 13862306a36Sopenharmony_ci rds->pty = alt ? 16 : 3; 13962306a36Sopenharmony_ci } 14062306a36Sopenharmony_ci rds->mono_stereo = true; 14162306a36Sopenharmony_ci rds->art_head = false; 14262306a36Sopenharmony_ci rds->compressed = false; 14362306a36Sopenharmony_ci rds->dyn_pty = false; 14462306a36Sopenharmony_ci rds->tp = true; 14562306a36Sopenharmony_ci rds->ta = alt; 14662306a36Sopenharmony_ci rds->ms = true; 14762306a36Sopenharmony_ci snprintf(rds->psname, sizeof(rds->psname), "%6d.%1d", 14862306a36Sopenharmony_ci (freq / 16) % 1000000, (((freq & 0xf) * 10) / 16) % 10); 14962306a36Sopenharmony_ci if (alt) 15062306a36Sopenharmony_ci strscpy(rds->radiotext, 15162306a36Sopenharmony_ci " The Radio Data System can switch between different Radio Texts ", 15262306a36Sopenharmony_ci sizeof(rds->radiotext)); 15362306a36Sopenharmony_ci else 15462306a36Sopenharmony_ci strscpy(rds->radiotext, 15562306a36Sopenharmony_ci "An example of Radio Text as transmitted by the Radio Data System", 15662306a36Sopenharmony_ci sizeof(rds->radiotext)); 15762306a36Sopenharmony_ci} 158