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
15static 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 */
42void 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, grp: 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(totalsecs: ktime_get_real_seconds(), offset: 0, result: &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: 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: grp % 22);
122 data[3].msb |= rds->pty >> 3;
123 data[3].msb |= 0x1f << 3;
124 break;
125 }
126 }
127}
128
129void 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(buf: rds->psname, size: sizeof(rds->psname), fmt: "%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

source code of linux/drivers/media/test-drivers/vivid/vivid-rds-gen.c