1 // SPDX-License-Identifier: GPL-2.0-only
3 * vivid-rds-gen.c - rds (radio data system) generator support functions.
5 * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
8 #include <linux/kernel.h>
9 #include <linux/ktime.h>
10 #include <linux/string.h>
11 #include <linux/videodev2.h>
13 #include "vivid-rds-gen.h"
15 static u8
vivid_get_di(const struct vivid_rds_gen
*rds
, unsigned grp
)
19 return (rds
->dyn_pty
<< 2) | (grp
& 3);
21 return (rds
->compressed
<< 2) | (grp
& 3);
23 return (rds
->art_head
<< 2) | (grp
& 3);
25 return (rds
->mono_stereo
<< 2) | (grp
& 3);
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.
35 * Groups 4-19 and 26-41 use group 2A for the radio text.
37 * Group 56 contains the time (group 4A).
39 * All remaining groups use a filler group 15B block that just repeats
40 * the PI and PTY codes.
42 void vivid_rds_generate(struct vivid_rds_gen
*rds
)
44 struct v4l2_rds_data
*data
= rds
->data
;
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);
64 case 44 ... 47: /* Group 0B */
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
];
76 case 26 ... 41: /* Group 2A */
77 idx
= ((grp
- 4) % 22) % 16;
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];
90 * Uses the algorithm from Annex G of the RDS standard
91 * EN 50067:1998 to convert a UTC date to an RDS Modified
94 time64_to_tm(ktime_get_real_seconds(), 0, &tm
);
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) |
100 (sys_tz
.tz_minuteswest
>= 0 ? 0x20 : 0) |
101 (abs(sys_tz
.tz_minuteswest
) / 30);
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;
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;
129 void vivid_rds_gen_fill(struct vivid_rds_gen
*rds
, unsigned freq
,
132 /* Alternate PTY between Info and Weather */
134 rds
->picode
= 0x2e75; /* 'KLNX' call sign */
135 rds
->pty
= alt
? 29 : 2;
137 rds
->picode
= 0x8088;
138 rds
->pty
= alt
? 16 : 3;
140 rds
->mono_stereo
= true;
141 rds
->art_head
= false;
142 rds
->compressed
= false;
143 rds
->dyn_pty
= false;
147 snprintf(rds
->psname
, sizeof(rds
->psname
), "%6d.%1d",
148 freq
/ 16, ((freq
& 0xf) * 10) / 16);
150 strlcpy(rds
->radiotext
,
151 " The Radio Data System can switch between different Radio Texts ",
152 sizeof(rds
->radiotext
));
154 strlcpy(rds
->radiotext
,
155 "An example of Radio Text as transmitted by the Radio Data System",
156 sizeof(rds
->radiotext
));