aboutsummaryrefslogtreecommitdiffstats
path: root/i2c_loader.vhd
diff options
context:
space:
mode:
authorMike Stirling <opensource@mikestirling.co.uk>2011-07-31 13:01:46 +0100
committerMike Stirling <opensource@mikestirling.co.uk>2011-07-31 13:01:46 +0100
commitb8eed4413b7d093d8df73dc2c5ffc83462a4a2ed (patch)
treeead2c35909b0f50befd0872c6631b69f7eab6d08 /i2c_loader.vhd
parent263ebee92985bfd0d9e2c894dfbfc5c34e270159 (diff)
downloadfpga-bbc-b8eed4413b7d093d8df73dc2c5ffc83462a4a2ed.tar.gz
fpga-bbc-b8eed4413b7d093d8df73dc2c5ffc83462a4a2ed.tar.bz2
fpga-bbc-b8eed4413b7d093d8df73dc2c5ffc83462a4a2ed.zip
Added sound generator support and interface to WM8731L audio codec. Uses SN76489 implementation from FPGA arcade.
Diffstat (limited to 'i2c_loader.vhd')
-rw-r--r--i2c_loader.vhd300
1 files changed, 300 insertions, 0 deletions
diff --git a/i2c_loader.vhd b/i2c_loader.vhd
new file mode 100644
index 0000000..385d9c5
--- /dev/null
+++ b/i2c_loader.vhd
@@ -0,0 +1,300 @@
+-- ZX Spectrum for Altera DE1
+--
+-- Copyright (c) 2009-2010 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 permission.
+--
+-- 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;
+