-
Notifications
You must be signed in to change notification settings - Fork 5
/
i2c_loader.vhd
304 lines (285 loc) · 8.83 KB
/
i2c_loader.vhd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
-- ZX Spectrum for Altera DE1
--
-- Copyright (c) 2009-2011 Mike Stirling
--
-- All rights reserved
--
-- Redistribution and use in source and synthezised forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- * Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- * Redistributions in synthesized form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- * Neither the name of the author nor the names of other contributors may
-- be used to endorse or promote products derived from this software without
-- specific prior written agreement from the author.
--
-- * License is granted for non-commercial use only. A fee may not be charged
-- for redistributions as source code or in synthesized/hardware form without
-- specific prior written agreement from the author.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
-- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use IEEE.STD_LOGIC_MISC.ALL; -- for AND_REDUCE
use IEEE.NUMERIC_STD.ALL;
entity i2c_loader is
generic (
-- Address of slave to be loaded
device_address : integer := 16#1a#;
-- Number of retries to allow before stopping
num_retries : integer := 0;
-- Length of clock divider in bits. Resulting bus frequency is
-- CLK/2^(log2_divider + 2)
log2_divider : integer := 6
);
port (
CLK : in std_logic;
nRESET : in std_logic;
I2C_SCL : inout std_logic;
I2C_SDA : inout std_logic;
IS_DONE : out std_logic;
IS_ERROR : out std_logic
);
end i2c_loader;
architecture i2c_loader_arch of i2c_loader is
type regs is array(0 to 19) of std_logic_vector(7 downto 0);
constant init_regs : regs := (
-- Left line in, 0dB, unmute
X"00", X"17",
-- Right line in, 0dB, unmute
X"02", X"17",
-- Left headphone out, 0dB
X"04", X"79",
-- Right headphone out, 0dB
X"06", X"79",
-- Audio path, DAC enabled, Line in, Bypass off, mic unmuted
X"08", X"10",
-- Digital path, Unmute, HP filter enabled
X"0A", X"00",
-- Power down mic, clkout and xtal osc
X"0C", X"62",
-- Format 16-bit I2S, no bit inversion or phase changes
X"0E", X"02",
-- Sampling control, 8 kHz USB mode (MCLK = 250fs * 6)
X"10", X"0D",
-- Activate
X"12", X"01"
);
-- Number of bursts (i.e. total number of registers)
constant burst_length : positive := 2;
-- Number of bytes to transfer per burst
constant num_bursts : positive := (init_regs'length / burst_length);
type state_t is (Idle, Start, Data, Ack, Stop, Pause, Done);
signal state : state_t;
signal phase : std_logic_vector(1 downto 0);
subtype nbit_t is integer range 0 to 7;
signal nbit : nbit_t;
subtype nbyte_t is integer range 0 to burst_length; -- +1 for address byte
signal nbyte : nbyte_t;
subtype thisbyte_t is integer range 0 to init_regs'length; -- +1 for "done"
signal thisbyte : thisbyte_t;
subtype retries_t is integer range 0 to num_retries;
signal retries : retries_t;
signal clken : std_logic;
signal divider : std_logic_vector(log2_divider-1 downto 0);
signal shiftreg : std_logic_vector(7 downto 0);
signal scl_out : std_logic;
signal sda_out : std_logic;
signal nak : std_logic;
begin
-- Create open-drain outputs for I2C bus
I2C_SCL <= '0' when scl_out = '0' else 'Z';
I2C_SDA <= '0' when sda_out = '0' else 'Z';
-- Status outputs are driven both ways
IS_DONE <= '1' when state = Done else '0';
IS_ERROR <= nak;
-- Generate clock enable for desired bus speed
clken <= AND_REDUCE(divider);
process(nRESET,CLK)
begin
if nRESET = '0' then
divider <= (others => '0');
elsif falling_edge(CLK) then
divider <= divider + '1';
end if;
end process;
-- The I2C loader process
process(nRESET,CLK)
begin
if nRESET = '0' then
scl_out <= '1';
sda_out <= '1';
state <= Idle;
phase <= "00";
nbit <= 0;
nbyte <= 0;
thisbyte <= 0;
shiftreg <= (others => '0');
nak <= '0'; -- No error
retries <= num_retries;
elsif rising_edge(CLK) and clken = '1' then
-- Next phase by default
phase <= phase + 1;
-- STATE: IDLE
if state = Idle then
-- Start loading the device registers straight away
-- A 'GO' bit could be polled here if required
state <= Start;
phase <= "00";
scl_out <= '1';
sda_out <= '1';
-- STATE: START
elsif state = Start then
-- Generate START condition
case phase is
when "00" =>
-- Drop SDA first
sda_out <= '0';
when "10" =>
-- Then drop SCL
scl_out <= '0';
when "11" =>
-- Advance to next state
-- Shift register loaded with device slave address
state <= Data;
nbit <= 7;
shiftreg <= std_logic_vector(to_unsigned(device_address,7)) & '0'; -- writing
nbyte <= burst_length;
when others =>
null;
end case;
-- STATE: DATA
elsif state = Data then
-- Generate data
case phase is
when "00" =>
-- Drop SCL
scl_out <= '0';
when "01" =>
-- Output data and shift (MSb first)
sda_out <= shiftreg(7);
shiftreg <= shiftreg(6 downto 0) & '0';
when "10" =>
-- Raise SCL
scl_out <= '1';
when "11" =>
-- Next bit or advance to next state when done
if nbit = 0 then
state <= Ack;
else
nbit <= nbit - 1;
end if;
when others =>
null;
end case;
-- STATE: ACK
elsif state = Ack then
-- Generate ACK clock and check for error condition
case phase is
when "00" =>
-- Drop SCL
scl_out <= '0';
when "01" =>
-- Float data
sda_out <= '1';
when "10" =>
-- Sample ack bit
nak <= I2C_SDA;
if I2C_SDA = '1' then
-- Error
nbyte <= 0; -- Close this burst and skip remaining registers
thisbyte <= init_regs'length;
else
-- Hold ACK to avoid spurious stops - this seems to fix a
-- problem with the Wolfson codec which releases the ACK
-- right on the falling edge of the clock pulse. It looks like
-- the device interprets this is a STOP condition and then fails
-- to acknowledge the next byte. We can avoid this by holding the
-- ACK condition for a little longer.
sda_out <= '0';
end if;
-- Raise SCL
scl_out <= '1';
when "11" =>
-- Advance to next state
if nbyte = 0 then
-- No more bytes in this burst - generate a STOP
state <= Stop;
else
-- Generate next byte
state <= Data;
nbit <= 7;
shiftreg <= init_regs(thisbyte);
nbyte <= nbyte - 1;
thisbyte <= thisbyte + 1;
end if;
when others =>
null;
end case;
-- STATE: STOP
elsif state = Stop then
-- Generate STOP condition
case phase is
when "00" =>
-- Drop SCL first
scl_out <= '0';
when "01" =>
-- Drop SDA
sda_out <= '0';
when "10" =>
-- Raise SCL
scl_out <= '1';
when "11" =>
if thisbyte = init_regs'length then
-- All registers done, advance to finished state. This will
-- bring SDA high while SCL is still high, completing the STOP
-- condition
state <= Done;
else
-- Load the next register after a short delay
state <= Pause;
end if;
when others =>
null;
end case;
-- STATE: PAUSE
elsif state = Pause then
-- Delay for one cycle of 'phase' then start the next burst
scl_out <= '1';
sda_out <= '1';
if phase = "11" then
state <= Start;
end if;
-- STATE: DONE
else
-- Finished
scl_out <= '1';
sda_out <= '1';
if nak = '1' and retries > 0 then
-- We can retry in the event of a NAK in case the
-- slave got out of sync for some reason
retries <= retries - 1;
state <= Idle;
end if;
end if;
end if;
end process;
end i2c_loader_arch;