18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * vivid-rds-gen.c - rds (radio data system) generator support functions. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/kernel.h> 98c2ecf20Sopenharmony_ci#include <linux/ktime.h> 108c2ecf20Sopenharmony_ci#include <linux/string.h> 118c2ecf20Sopenharmony_ci#include <linux/videodev2.h> 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include "vivid-rds-gen.h" 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_cistatic u8 vivid_get_di(const struct vivid_rds_gen *rds, unsigned grp) 168c2ecf20Sopenharmony_ci{ 178c2ecf20Sopenharmony_ci switch (grp) { 188c2ecf20Sopenharmony_ci case 0: 198c2ecf20Sopenharmony_ci return (rds->dyn_pty << 2) | (grp & 3); 208c2ecf20Sopenharmony_ci case 1: 218c2ecf20Sopenharmony_ci return (rds->compressed << 2) | (grp & 3); 228c2ecf20Sopenharmony_ci case 2: 238c2ecf20Sopenharmony_ci return (rds->art_head << 2) | (grp & 3); 248c2ecf20Sopenharmony_ci case 3: 258c2ecf20Sopenharmony_ci return (rds->mono_stereo << 2) | (grp & 3); 268c2ecf20Sopenharmony_ci } 278c2ecf20Sopenharmony_ci return 0; 288c2ecf20Sopenharmony_ci} 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* 318c2ecf20Sopenharmony_ci * This RDS generator creates 57 RDS groups (one group == four RDS blocks). 328c2ecf20Sopenharmony_ci * Groups 0-3, 22-25 and 44-47 (spaced 22 groups apart) are filled with a 338c2ecf20Sopenharmony_ci * standard 0B group containing the PI code and PS name. 348c2ecf20Sopenharmony_ci * 358c2ecf20Sopenharmony_ci * Groups 4-19 and 26-41 use group 2A for the radio text. 368c2ecf20Sopenharmony_ci * 378c2ecf20Sopenharmony_ci * Group 56 contains the time (group 4A). 388c2ecf20Sopenharmony_ci * 398c2ecf20Sopenharmony_ci * All remaining groups use a filler group 15B block that just repeats 408c2ecf20Sopenharmony_ci * the PI and PTY codes. 418c2ecf20Sopenharmony_ci */ 428c2ecf20Sopenharmony_civoid vivid_rds_generate(struct vivid_rds_gen *rds) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci struct v4l2_rds_data *data = rds->data; 458c2ecf20Sopenharmony_ci unsigned grp; 468c2ecf20Sopenharmony_ci unsigned idx; 478c2ecf20Sopenharmony_ci struct tm tm; 488c2ecf20Sopenharmony_ci unsigned date; 498c2ecf20Sopenharmony_ci unsigned time; 508c2ecf20Sopenharmony_ci int l; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci for (grp = 0; grp < VIVID_RDS_GEN_GROUPS; grp++, data += VIVID_RDS_GEN_BLKS_PER_GRP) { 538c2ecf20Sopenharmony_ci data[0].lsb = rds->picode & 0xff; 548c2ecf20Sopenharmony_ci data[0].msb = rds->picode >> 8; 558c2ecf20Sopenharmony_ci data[0].block = V4L2_RDS_BLOCK_A | (V4L2_RDS_BLOCK_A << 3); 568c2ecf20Sopenharmony_ci data[1].lsb = rds->pty << 5; 578c2ecf20Sopenharmony_ci data[1].msb = (rds->pty >> 3) | (rds->tp << 2); 588c2ecf20Sopenharmony_ci data[1].block = V4L2_RDS_BLOCK_B | (V4L2_RDS_BLOCK_B << 3); 598c2ecf20Sopenharmony_ci data[3].block = V4L2_RDS_BLOCK_D | (V4L2_RDS_BLOCK_D << 3); 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci switch (grp) { 628c2ecf20Sopenharmony_ci case 0 ... 3: 638c2ecf20Sopenharmony_ci case 22 ... 25: 648c2ecf20Sopenharmony_ci case 44 ... 47: /* Group 0B */ 658c2ecf20Sopenharmony_ci idx = (grp % 22) % 4; 668c2ecf20Sopenharmony_ci data[1].lsb |= (rds->ta << 4) | (rds->ms << 3); 678c2ecf20Sopenharmony_ci data[1].lsb |= vivid_get_di(rds, idx); 688c2ecf20Sopenharmony_ci data[1].msb |= 1 << 3; 698c2ecf20Sopenharmony_ci data[2].lsb = rds->picode & 0xff; 708c2ecf20Sopenharmony_ci data[2].msb = rds->picode >> 8; 718c2ecf20Sopenharmony_ci data[2].block = V4L2_RDS_BLOCK_C_ALT | (V4L2_RDS_BLOCK_C_ALT << 3); 728c2ecf20Sopenharmony_ci data[3].lsb = rds->psname[2 * idx + 1]; 738c2ecf20Sopenharmony_ci data[3].msb = rds->psname[2 * idx]; 748c2ecf20Sopenharmony_ci break; 758c2ecf20Sopenharmony_ci case 4 ... 19: 768c2ecf20Sopenharmony_ci case 26 ... 41: /* Group 2A */ 778c2ecf20Sopenharmony_ci idx = ((grp - 4) % 22) % 16; 788c2ecf20Sopenharmony_ci data[1].lsb |= idx; 798c2ecf20Sopenharmony_ci data[1].msb |= 4 << 3; 808c2ecf20Sopenharmony_ci data[2].msb = rds->radiotext[4 * idx]; 818c2ecf20Sopenharmony_ci data[2].lsb = rds->radiotext[4 * idx + 1]; 828c2ecf20Sopenharmony_ci data[2].block = V4L2_RDS_BLOCK_C | (V4L2_RDS_BLOCK_C << 3); 838c2ecf20Sopenharmony_ci data[3].msb = rds->radiotext[4 * idx + 2]; 848c2ecf20Sopenharmony_ci data[3].lsb = rds->radiotext[4 * idx + 3]; 858c2ecf20Sopenharmony_ci break; 868c2ecf20Sopenharmony_ci case 56: 878c2ecf20Sopenharmony_ci /* 888c2ecf20Sopenharmony_ci * Group 4A 898c2ecf20Sopenharmony_ci * 908c2ecf20Sopenharmony_ci * Uses the algorithm from Annex G of the RDS standard 918c2ecf20Sopenharmony_ci * EN 50067:1998 to convert a UTC date to an RDS Modified 928c2ecf20Sopenharmony_ci * Julian Day. 938c2ecf20Sopenharmony_ci */ 948c2ecf20Sopenharmony_ci time64_to_tm(ktime_get_real_seconds(), 0, &tm); 958c2ecf20Sopenharmony_ci l = tm.tm_mon <= 1; 968c2ecf20Sopenharmony_ci date = 14956 + tm.tm_mday + ((tm.tm_year - l) * 1461) / 4 + 978c2ecf20Sopenharmony_ci ((tm.tm_mon + 2 + l * 12) * 306001) / 10000; 988c2ecf20Sopenharmony_ci time = (tm.tm_hour << 12) | 998c2ecf20Sopenharmony_ci (tm.tm_min << 6) | 1008c2ecf20Sopenharmony_ci (sys_tz.tz_minuteswest >= 0 ? 0x20 : 0) | 1018c2ecf20Sopenharmony_ci (abs(sys_tz.tz_minuteswest) / 30); 1028c2ecf20Sopenharmony_ci data[1].lsb &= ~3; 1038c2ecf20Sopenharmony_ci data[1].lsb |= date >> 15; 1048c2ecf20Sopenharmony_ci data[1].msb |= 8 << 3; 1058c2ecf20Sopenharmony_ci data[2].lsb = (date << 1) & 0xfe; 1068c2ecf20Sopenharmony_ci data[2].lsb |= (time >> 16) & 1; 1078c2ecf20Sopenharmony_ci data[2].msb = (date >> 7) & 0xff; 1088c2ecf20Sopenharmony_ci data[2].block = V4L2_RDS_BLOCK_C | (V4L2_RDS_BLOCK_C << 3); 1098c2ecf20Sopenharmony_ci data[3].lsb = time & 0xff; 1108c2ecf20Sopenharmony_ci data[3].msb = (time >> 8) & 0xff; 1118c2ecf20Sopenharmony_ci break; 1128c2ecf20Sopenharmony_ci default: /* Group 15B */ 1138c2ecf20Sopenharmony_ci data[1].lsb |= (rds->ta << 4) | (rds->ms << 3); 1148c2ecf20Sopenharmony_ci data[1].lsb |= vivid_get_di(rds, grp % 22); 1158c2ecf20Sopenharmony_ci data[1].msb |= 0x1f << 3; 1168c2ecf20Sopenharmony_ci data[2].lsb = rds->picode & 0xff; 1178c2ecf20Sopenharmony_ci data[2].msb = rds->picode >> 8; 1188c2ecf20Sopenharmony_ci data[2].block = V4L2_RDS_BLOCK_C_ALT | (V4L2_RDS_BLOCK_C_ALT << 3); 1198c2ecf20Sopenharmony_ci data[3].lsb = rds->pty << 5; 1208c2ecf20Sopenharmony_ci data[3].lsb |= (rds->ta << 4) | (rds->ms << 3); 1218c2ecf20Sopenharmony_ci data[3].lsb |= vivid_get_di(rds, grp % 22); 1228c2ecf20Sopenharmony_ci data[3].msb |= rds->pty >> 3; 1238c2ecf20Sopenharmony_ci data[3].msb |= 0x1f << 3; 1248c2ecf20Sopenharmony_ci break; 1258c2ecf20Sopenharmony_ci } 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci} 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_civoid vivid_rds_gen_fill(struct vivid_rds_gen *rds, unsigned freq, 1308c2ecf20Sopenharmony_ci bool alt) 1318c2ecf20Sopenharmony_ci{ 1328c2ecf20Sopenharmony_ci /* Alternate PTY between Info and Weather */ 1338c2ecf20Sopenharmony_ci if (rds->use_rbds) { 1348c2ecf20Sopenharmony_ci rds->picode = 0x2e75; /* 'KLNX' call sign */ 1358c2ecf20Sopenharmony_ci rds->pty = alt ? 29 : 2; 1368c2ecf20Sopenharmony_ci } else { 1378c2ecf20Sopenharmony_ci rds->picode = 0x8088; 1388c2ecf20Sopenharmony_ci rds->pty = alt ? 16 : 3; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci rds->mono_stereo = true; 1418c2ecf20Sopenharmony_ci rds->art_head = false; 1428c2ecf20Sopenharmony_ci rds->compressed = false; 1438c2ecf20Sopenharmony_ci rds->dyn_pty = false; 1448c2ecf20Sopenharmony_ci rds->tp = true; 1458c2ecf20Sopenharmony_ci rds->ta = alt; 1468c2ecf20Sopenharmony_ci rds->ms = true; 1478c2ecf20Sopenharmony_ci snprintf(rds->psname, sizeof(rds->psname), "%6d.%1d", 1488c2ecf20Sopenharmony_ci (freq / 16) % 1000000, (((freq & 0xf) * 10) / 16) % 10); 1498c2ecf20Sopenharmony_ci if (alt) 1508c2ecf20Sopenharmony_ci strscpy(rds->radiotext, 1518c2ecf20Sopenharmony_ci " The Radio Data System can switch between different Radio Texts ", 1528c2ecf20Sopenharmony_ci sizeof(rds->radiotext)); 1538c2ecf20Sopenharmony_ci else 1548c2ecf20Sopenharmony_ci strscpy(rds->radiotext, 1558c2ecf20Sopenharmony_ci "An example of Radio Text as transmitted by the Radio Data System", 1568c2ecf20Sopenharmony_ci sizeof(rds->radiotext)); 1578c2ecf20Sopenharmony_ci} 158