1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * vivid-rds-gen.c - rds (radio data system) generator support functions. 4 * 5 * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 6 */ 7 8 #include <linux/kernel.h> 9 #include <linux/ktime.h> 10 #include <linux/string.h> 11 #include <linux/videodev2.h> 12 13 #include "vivid-rds-gen.h" 14 15 static u8 vivid_get_di(const struct vivid_rds_gen *rds, unsigned grp) 16 { 17 switch (grp) { 18 case 0: 19 return (rds->dyn_pty << 2) | (grp & 3); 20 case 1: 21 return (rds->compressed << 2) | (grp & 3); 22 case 2: 23 return (rds->art_head << 2) | (grp & 3); 24 case 3: 25 return (rds->mono_stereo << 2) | (grp & 3); 26 } 27 return 0; 28 } 29 30 /* 31 * This RDS generator creates 57 RDS groups (one group == four RDS blocks). 32 * Groups 0-3, 22-25 and 44-47 (spaced 22 groups apart) are filled with a 33 * standard 0B group containing the PI code and PS name. 34 * 35 * Groups 4-19 and 26-41 use group 2A for the radio text. 36 * 37 * Group 56 contains the time (group 4A). 38 * 39 * All remaining groups use a filler group 15B block that just repeats 40 * the PI and PTY codes. 41 */ 42 void vivid_rds_generate(struct vivid_rds_gen *rds) 43 { 44 struct v4l2_rds_data *data = rds->data; 45 unsigned grp; 46 unsigned idx; 47 struct tm tm; 48 unsigned date; 49 unsigned time; 50 int l; 51 52 for (grp = 0; grp < VIVID_RDS_GEN_GROUPS; grp++, data += VIVID_RDS_GEN_BLKS_PER_GRP) { 53 data[0].lsb = rds->picode & 0xff; 54 data[0].msb = rds->picode >> 8; 55 data[0].block = V4L2_RDS_BLOCK_A | (V4L2_RDS_BLOCK_A << 3); 56 data[1].lsb = rds->pty << 5; 57 data[1].msb = (rds->pty >> 3) | (rds->tp << 2); 58 data[1].block = V4L2_RDS_BLOCK_B | (V4L2_RDS_BLOCK_B << 3); 59 data[3].block = V4L2_RDS_BLOCK_D | (V4L2_RDS_BLOCK_D << 3); 60 61 switch (grp) { 62 case 0 ... 3: 63 case 22 ... 25: 64 case 44 ... 47: /* Group 0B */ 65 idx = (grp % 22) % 4; 66 data[1].lsb |= (rds->ta << 4) | (rds->ms << 3); 67 data[1].lsb |= vivid_get_di(rds, idx); 68 data[1].msb |= 1 << 3; 69 data[2].lsb = rds->picode & 0xff; 70 data[2].msb = rds->picode >> 8; 71 data[2].block = V4L2_RDS_BLOCK_C_ALT | (V4L2_RDS_BLOCK_C_ALT << 3); 72 data[3].lsb = rds->psname[2 * idx + 1]; 73 data[3].msb = rds->psname[2 * idx]; 74 break; 75 case 4 ... 19: 76 case 26 ... 41: /* Group 2A */ 77 idx = ((grp - 4) % 22) % 16; 78 data[1].lsb |= idx; 79 data[1].msb |= 4 << 3; 80 data[2].msb = rds->radiotext[4 * idx]; 81 data[2].lsb = rds->radiotext[4 * idx + 1]; 82 data[2].block = V4L2_RDS_BLOCK_C | (V4L2_RDS_BLOCK_C << 3); 83 data[3].msb = rds->radiotext[4 * idx + 2]; 84 data[3].lsb = rds->radiotext[4 * idx + 3]; 85 break; 86 case 56: 87 /* 88 * Group 4A 89 * 90 * Uses the algorithm from Annex G of the RDS standard 91 * EN 50067:1998 to convert a UTC date to an RDS Modified 92 * Julian Day. 93 */ 94 time64_to_tm(ktime_get_real_seconds(), 0, &tm); 95 l = tm.tm_mon <= 1; 96 date = 14956 + tm.tm_mday + ((tm.tm_year - l) * 1461) / 4 + 97 ((tm.tm_mon + 2 + l * 12) * 306001) / 10000; 98 time = (tm.tm_hour << 12) | 99 (tm.tm_min << 6) | 100 (sys_tz.tz_minuteswest >= 0 ? 0x20 : 0) | 101 (abs(sys_tz.tz_minuteswest) / 30); 102 data[1].lsb &= ~3; 103 data[1].lsb |= date >> 15; 104 data[1].msb |= 8 << 3; 105 data[2].lsb = (date << 1) & 0xfe; 106 data[2].lsb |= (time >> 16) & 1; 107 data[2].msb = (date >> 7) & 0xff; 108 data[2].block = V4L2_RDS_BLOCK_C | (V4L2_RDS_BLOCK_C << 3); 109 data[3].lsb = time & 0xff; 110 data[3].msb = (time >> 8) & 0xff; 111 break; 112 default: /* Group 15B */ 113 data[1].lsb |= (rds->ta << 4) | (rds->ms << 3); 114 data[1].lsb |= vivid_get_di(rds, grp % 22); 115 data[1].msb |= 0x1f << 3; 116 data[2].lsb = rds->picode & 0xff; 117 data[2].msb = rds->picode >> 8; 118 data[2].block = V4L2_RDS_BLOCK_C_ALT | (V4L2_RDS_BLOCK_C_ALT << 3); 119 data[3].lsb = rds->pty << 5; 120 data[3].lsb |= (rds->ta << 4) | (rds->ms << 3); 121 data[3].lsb |= vivid_get_di(rds, grp % 22); 122 data[3].msb |= rds->pty >> 3; 123 data[3].msb |= 0x1f << 3; 124 break; 125 } 126 } 127 } 128 129 void vivid_rds_gen_fill(struct vivid_rds_gen *rds, unsigned freq, 130 bool alt) 131 { 132 /* Alternate PTY between Info and Weather */ 133 if (rds->use_rbds) { 134 rds->picode = 0x2e75; /* 'KLNX' call sign */ 135 rds->pty = alt ? 29 : 2; 136 } else { 137 rds->picode = 0x8088; 138 rds->pty = alt ? 16 : 3; 139 } 140 rds->mono_stereo = true; 141 rds->art_head = false; 142 rds->compressed = false; 143 rds->dyn_pty = false; 144 rds->tp = true; 145 rds->ta = alt; 146 rds->ms = true; 147 snprintf(rds->psname, sizeof(rds->psname), "%6d.%1d", 148 (freq / 16) % 1000000, (((freq & 0xf) * 10) / 16) % 10); 149 if (alt) 150 strscpy(rds->radiotext, 151 " The Radio Data System can switch between different Radio Texts ", 152 sizeof(rds->radiotext)); 153 else 154 strscpy(rds->radiotext, 155 "An example of Radio Text as transmitted by the Radio Data System", 156 sizeof(rds->radiotext)); 157 } 158