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, 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 | |
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(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 | |