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