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