Writing a Linux Device Driver for VS-1003/VS-1053 Audio Codec on Raspberry Pi 3 with Linux Kernel 4.4+


A Linux Device Driver for Audio Codec, VS-1003/VS-1053 on Linux kernel 4.4 for Rpi3

Introduction


VS1003/VS1053 is a single-chip MP3/WMA/MIDI Audio Decoder and ADPCM encoder. It contains a high-performance, proprietary low-power DSP processor core VS_DSP4, working data memory, 5 KiB instruction RAM and 0.5 KiB data RAM for user applications, serial control and input data interfaces, 4 general purpose I/O pins, an UART, as well as a high-quality variable sample-rate mono ADC and stereo DAC, followed by an earphone amplifier and a common buffer.

VS1003 receives its input bitstream through a serial input bus, which it listens to as a system slave. The input stream is decoded and passed through a digital volume control to an 18-bit oversampling, multi-bit, sigma-delta DAC. The decoding is controlled via a serial control bus. In addition to the basic decoding, it is possible to add application specific features, like DSP effects, to the user RAM memory.

A few features that can be listed are as follows :

Decodes MPEG 1 & 2 audio layer III (CBR +VBR +ABR); WMA 4.0/4.1/7/8/9 all profiles (5-384kbit/s); WAV (PCM + IMA ADPCM); General MIDI / SP-MIDI files
Encodes IMA ADPCM from microphone or line input
Streaming support for MP3 and WAV
Bass and treble controls
Operates with a single 12..13 MHz clock
Internal PLL clock multiplier
Low-power operation
High-quality on-chip stereo DAC with no phase error between channels
Stereo earphone driver capable of driving a 30Ohm load
Separate operating voltages for analog, digital and I/O
5.5 KiB On-chip RAM for user code / data
Serial control and Data Interfaces
Can be used as a slave co-processor
SPI flash boot for special applications
UART for debugging purposes
New functions may be added with software and 4 GPIO pins

The Block diagram is as given below:

























The VS-1003/VS-1053 Board that I have considered for the Project is from eBay, which comes pretty cheap for around 12$. Below is the link:

http://www.ebay.in/itm/172907369609?aff_source=Sok-Goog
























The requirement of my Project requires the Audio Codec to serve as a Slave while my Raspberry Pi3 to serve as the master. The schematic of the Board is as shown below. The Input Voltage to the Board can be either 5V or 3.3V, but not both at the same time. The P3 Expansion header as shown in the schematic below exposes the SPI Communication Interfaces, DREQ & RESET Signals, the Vcc & the Gnd signals. It would have been better if the GPIOs, UART Tx-Rx and Test Pins of VS1003 Chip were also exposed on the expansion header, P3.

From the Schematic we can see that apart from Audio Playback, Audio Capture or Audio Record Interface has also been provided in the form of a 3.5 mm Jack Line-IN input to Pin 48 of Audio Codec Chip & a MicroPhone breakout input to MICP & MICN, Pin 1 & Pin 2 of the Audio Codec Chip. However, an Audio Detect Pin & a Audio Detect signal is missing which would have been useful to determine PLAY/MUTE Status.


















































This goes all well for the Slave which is our Audio Codec, VS1003/VS1053. Now something about our Master which will be the RaspberryPi3. The Raspberry Pi 3 is a version of the RaspberryPi which was released in February 2016. It contains a 1.2 GHz ARM Cortex-A53 CPU and hence is the first version of the RaspberryPi to support the arm64 architecture.  The Master is my Raspberry Pi 3, Model B, Ver1.2 running on Linux Kernel 4.4.50 Raspbian Image, 2017-04-10-raspbian-jessie. I have particularly considered this Release since this will be the Kernel version for the Kernel that ships in with this Release for which we will be developing our Linux Driver for. People can download & install the Raspbian Image on a micro-SD Card, using Etcher or Win32DiskImager Tool from the below link:

http://ftp.jaist.ac.jp/pub/raspberrypi/raspbian/images/raspbian-2017-04-10/



















After, successfully flashing the Raspbian Image on our microSD Card we are ready to lock & load on our Raspberry Pi 3 & we power it up and up comes the console,












































Pin Connection Diagram & Schematics


The Pin Connection diagram between the VS-1003 Board & my Raspberry Pi 3 board can be shown as below:







As,we can see, we have assigned two GPIO Pins of Rpi3, GPIO 26 to xRST, Reset Pin & GPIO 13 to DREQ Pin of the Audio Codec Board. The two Chip select, SPI0.CS0 & SPI0.CS1 of Rpi3 are assigned to control the Chip select lines for SCI & SDI interfaces of the Audio Codec VS1003/VS1053. SPI_CE0_N is assigned to xCS & SPI_CE1_N is assigned to xDCS of the Audio Codec. And the remaining SPI Standard Signals SPI_MISO, SPI_MOSI & SPI_CLK of Rpi3 are fed to the corresponding MISO, MOSI & SCLK lines of the Audio Codec.


Data Write & Transfer to Audio Codec - Operation summary


The way in which we transfer the Encoded MP3 Bytes from Rpi3 to VS1053 is by calling the WRITE system call to VS1053 Linux Driver. I have created two Queue Buffers here in my Driver. The two Queue buffers are the Pool Queue Buffer & the Data Queue Buffer. Initially the Pool Queue buffer is initialized & allocated with 2048 x 32-bytes of Transmit array element & the Data Queue Buffer is initialized with only Head element. The below fig. shows the initial state,




























The way in which we transfer the Encoded MP3 Bytes from Rpi3 to VS1053 is by calling the WRITE system call to VS1053 Linux Driver. I have created two Queue Buffers here in my Driver. The two Queue buffers are the Pool Queue Buffer & the Data Queue Buffer. Initially the Pool Queue buffer is initialized & allocated with 2048 x 32-bytes of Transmit array element & the Data Queue Buffer is initialized with only Head element. The below fig. shows the initial state,

During the WRITE System call we would do something like this with our Queues.

1. From, the Pool Queue Buffer Head we take our 32-byte element

2. Move the Head to the next element in the Pool queue buffer

3. Copy the 32-bytes of Mp3 Data passed from the User space buffer using copy_from_user kernel method to the 32-byte Queue Head element we took out in step 1.

4. After copy the 32-bytes of User space buffer, we move & fix the Queue element to the Tail of our Data queue buffer. Thus the Tail of the Data Queue keeps growing while at the same time the Head of the Pool queue buffer keeps shrinking as this operation is continued for 2048 times, which is total number of our Pool queue buffer 32-byte transmit samples.

The operation is illustrated as below:




Next, after we have copied these 2048 samples of each 32-byte to Data queue buffer, we will Transmit these 2048 samples to our Mp3 Audio Codec, using SPI. In this regard we will use the SDI Interface. We can illustrate the TRANSMIT operation as shown below. We can see the TRANSMIT Operation unfolds in the following way:

1.  We take out the first, 32-byte element at Head of the Data queue buffer.

2.  Move the Head to the next element in the Data queue buffer.

3.  Copy the 32-byte Data to Transmit array & call the function, 'vs10xx_io_data_tx' in which we perform the SPI transfer to the Audio Codec.

4.  After successful Transfer we put the 32-byte element back to the Pool queue buffer by adding it to the Tail of the same. We repeat this operation from step 1. for another 2048 times, during which we see just the converse of what we saw during the WRITE, the Pool Queue buffer now keeps growing while the Data Queue buffer keeps shrinking at the same rate.




Thus, after the end of the Write & Transmit operation we have exactly the same structure, as when we started. Thus this PING PONG Buffer operation goes on during each WRITE of 2048 Stereo Mp3 samples to our Audio Codec, VS1053.



Writing the Device Tree for Audio Codec VS1053


Before Device Tree, the kernel drivers themselves contained the entire description of the hardware they control, memory addresses, IO ranges, registers addresses, etc. In this method, the Linux kernel must be modified/compiled for each board or change in hardware on board. Kernels are typically built around a single board file and cannot boot on any other type of system. Solution of this situation is provided by “Device tree”. 

In the Device Tree Era, the device tree mechanism centralizes all hardware information inside description text files, suffix .dts, dtsi, which makes the maintenance of hardware configuration easier and more reliable, drivers code are more generic and efficient.Then these dts files are compiled into dtb, device tree blob, which is a dt binary file, could be read and parsed by kernel to get all data about a certain node/device.

The device tree is passed to the kernel at boot time. Kernel reads through it to learn about what kind of system it is. So on the change of board only developer needs to change device tree blob and that it new port of kernel is ready.

Device Tree Compiler - The device tree compiler source code is located at scripts/dtc/ directory. During a build process, device tree will be compiled with the compiler from dts/dtsi files into dtb files.

So, first get the Kernel sources, for Linux Kernel 4.4

git clone https://github.com/raspberrypi/linux/tree/rpi-4.4.y --branch rpi-4.4.y

Go in the path /linux/arch/arm/boot/dts/overlays. The idea here would be to create our dts overlay file for the Audio Codec. An overlay sits on top of an existing filesystem, and combines an upper and a lower directory tree (which can be from different filesystems), in order to present a unified representation of both directory trees. 

   fragment@8 {
        target = <&spi0>;
        __dormant__ {
            status = "okay";
            #address-cells = <1>;
            #size-cells = <0>;

            mcp3008_00: mcp3008@0 {
                compatible = "vs1003-ctrl";
                reg = <0>;
                device_id = <0>;
                gpio_reset = <26>;
                gpio_dreq = <13>;
                spi-max-frequency = <16000000>;
            };
        };
    };

    fragment@9 {
        target = <&spi0>;
        __dormant__ {
            status = "okay";
            #address-cells = <1>;
            #size-cells = <0>;

            mcp3008_01: mcp3008@1 {
                compatible = "vs1003-data";
                reg = <1>;
                device_id = <0>;
                spi-max-frequency = <16000000>;
            };
        };
    };

Here, target = <&spi0> is a phandle (pointer handle). A phandle is a 32-bit value associated with a node that is used to uniquely identify that node so that the node can be reference from a property in another node. More simply put, it is a property in one node that contains a pointer to another node. 

Here, in this overlay we have the 'reg', register address property which is quite important as it specifies the SPI0_CS0 for reg = <0> & SPI0_CS1 for reg = <1>. The property gpio_reset & gpio_dreq are assigned with values 26 & 13. The property device_id is set to 0 & the spi-max-frequency is set to 16 MHz, which i feel would be more than enough to play Mp3 files at high bit rates reasonably well enough. 

Now, the most important property here is the compatible property.  We can see that the hardware is compatible with vs1003-data & vs1003-ctrl. This compatible property is used by the kernel to identify the hardware and match a driver that is registered in the kernel.

static const struct of_device_id vs10xx_ctrl_id[] = {
    {
        .compatible = "vs1001-ctrl",
    }, {
        .compatible = "vs1011-ctrl",
    }, {
        .compatible = "vs1002-ctrl",
    }, {
        .compatible = "vs1003-ctrl",
    }, {
        .compatible = "vs1053-ctrl",
    }, {
        .compatible = "vs1033-ctrl",
    }, {
        .compatible = "vs1103-ctrl",
    }, {
    }
};

MODULE_DEVICE_TABLE(of, vs10xx_ctrl_id);

static struct spi_driver vs10xx_spi_ctrl = {
    .driver = {
        .name = "vs10xx-ctrl",
        .of_match_table = of_match_ptr(vs10xx_ctrl_id),
    },
    .probe        = vs10xx_spi_ctrl_probe,
};


static const struct of_device_id vs10xx_data_id[] = {
    {
        .compatible = "vs1001-data",
    }, {
        .compatible = "vs1011-data",
    }, {
        .compatible = "vs1002-data",
    }, {
        .compatible = "vs1003-data",
    }, {
        .compatible = "vs1053-data",
    }, {
        .compatible = "vs1033-data",
    }, {
        .compatible = "vs1103-data",
    }, {
    }
};

MODULE_DEVICE_TABLE(of, vs10xx_data_id);

static struct spi_driver vs10xx_spi_data = {
    .driver = {
        .name = "vs10xx-data",
        .of_match_table = of_match_ptr(vs10xx_data_id),
    },
    .probe        = vs10xx_spi_data_probe,
};

In the source, we can see above that "vs1003-ctrl" and "vs1003-data" are registered with vs10xx_data_id & vs10xx_ctrl_id. This is the reason why this particular driver would be used.

Probe is defined as vs10xx_spi_ctrl_probe & vs10xx_spi_data_probe, which indicates the function used to parse the device tree. We can examine this function to see exactly where and how the properties in the device tree node for this module are used.

static int vs10xx_spi_ctrl_probe(struct spi_device *spi) {

    int status = 0, device_id =0xff, gpio_reset =0x00, gpio_dreq =0x00;
    void* ptr1;
    printk("Inside vs10xx_spi_ctrl_probe\n");

    ptr1 = of_get_property(spi->dev.of_node, "device_id", NULL);
    of_property_read_u32(spi->dev.of_node, "device_id", &device_id);
    of_property_read_u32(spi->dev.of_node, "gpio_reset", &gpio_reset);
    of_property_read_u32(spi->dev.of_node, "gpio_dreq", &gpio_dreq);
    if (ptr1 == NULL) {
        vs10xx_err("no property, device_id provided for vs10xx device");
        status = -1;
    }
    printk("Read vs10xx dev_id    : %d\n", device_id);
    printk("Read vs10xx reset gpio: %d\n", gpio_reset);
    printk("Read vs10xx dreq gpio : %d\n", gpio_dreq);
    vs10xx_chips[device_id].spi_ctrl   = (struct spi_device *)spi;
    vs10xx_chips[device_id].gpio_reset = gpio_reset;
    vs10xx_chips[device_id].gpio_dreq  = gpio_dreq;
    printk("Read vs10xx_chips spi_ctrl : 0x%x\n", (unsigned int)vs10xx_chips[device_id].spi_ctrl);

    return status;
}

static int vs10xx_spi_data_probe(struct spi_device *spi) {

    int status = 0, device_id =0xff;    
    printk("Inside vs10xx_spi_data_probe\n");

    of_property_read_u32(spi->dev.of_node, "device_id", &device_id);
    vs10xx_chips[device_id].spi_data = (struct spi_device *)spi;
    printk("Read vs10xx dev_id    : %d\n", device_id);
    printk("Read vs10xx_chips spi_data : 0x%x\n", (unsigned int)vs10xx_chips[device_id].spi_data);

    return status;
}


We can see that the same function pointers vs10xx_spi_ctrl_probe & vs10xx_spi_data_probe are defined in the .probe section of device. We can see how these methods of_get_property & of_property_read_us32 are used to Read property values. A brief summary of these methods can be listed as below:


The Read values of device_id, gpio_reset & gpio_dreq should be 0, 26 & 13, if the Device Tree has been correctly initialized. Lastly, an important thing that we do here in this section is assigning the spi_device structure elements of the struct vs10xx_chip. The vs10xx_chips[device_id].spi_data & vs10xx_chips[device_id].spi_ctrl are of struct spi_device type which is initialized to the device node corresponding to spi0_cs1 & spi0_cs0. This is a very important part which needs to be done correctly for the correct operation of the Driver.


Who calls the probe ? 


People might be wondering now, "But then, who calls the Probe ?" 

The answer would be during spi_register_driver call. If we look at the spi_register_driver call, we see the following. The spi_register_driver is passed with the Device pointer to the spi_driver member, which are vs10xx_spi_ctrl & vs10xx_spi_data respectively. The Probe handle is assigned inside the spi_driver via .probe & it is this handle which is a pointer to a function that is respectively called up at the time of register, when we call spi_register_driver.


    /* register vs10xx ctrl spi driver */
    s1 = spi_register_driver(&vs10xx_spi_ctrl);
    if (s1 < 0) {
        vs10xx_err("spi_register_driver: ctrl");
    }

    /* register vs10xx data spi driver */
    s2 = spi_register_driver(&vs10xx_spi_data);
    if (s2 < 0) {
        vs10xx_err("spi_register_driver: data");
    }

This completes the discussion on my design & concept of the Device Tree for the Audio Codec. Next, we shall see something about READ/WRITE to our VS1053 Audio Codec.


READ/WRITE to VS1053/VS1003 Audio Codec Register
s


Before I start this section, i would ask to download the Datasheets of VS1053/VS1003 DSP Audio Codec chip. The VLSI solutions are freely providing it. Given is the link below:

http://www.vlsi.fi/fileadmin/datasheets/vs1053.pdf

http://www.vlsi.fi/fileadmin/datasheets/vs1003.pdf

The SCI Interface of VS1003/VS1053 Audio Codec defines the way to READ/WRITE to the SCI Registers. If we look in the Datasheet of VS1053, we find in the section "7.5.1, Serial Protocol for Serial Command Interface (SCI)" the respective SPI Bus Timing diagrams & the requirements. The serial bus protocol for the Serial Command Interface SCI (Chapter 8.6) consists of an instruction byte, address byte and one 16-bit data word. Each read or write operation can READ or WRITE a single Register.

























From this we can see that the SPI READ operation precedes with 0x03 while the SPI WRITE operation precedes with 0x02 opcode. The below shows the Timing diagram of SCI Read,



















First, xCS line is pulled low to select the device. Then the READ opcode (0x03) is transmitted via the MOSI line followed by an 8-bit word address. After the address has been read in, any further data on MOSI is ignored by the chip.The 16-bit Data corresponding to the received address will be shifted out onto the MISO line. XCS should be driven high after data has been shifted out.

DREQ is driven low for a short while when in a read operation by the chip. This is a very short time and doesn’t require any special attention.

The Timing diagram for SCI WRITE can be shown as below,

























First, xCS line is pulled low to select the device. Then the WRITE opcode (0x2) is transmitted via the MOSI line followed by an 8-bit word address. After the word has been shifted in on the MOSI line and the last clock has been sent, xCS should be pulled high to end the WRITE sequence.

After the last bit has been sent, DREQ is driven low for the duration of the register update, marked “execution” in the figure.

In our Linux Driver we have handled the above SPI READ/WRITE using SPI Message system. A spi_message is used to execute an atomic sequence of data transfers, each represented by a struct spi_transfer. The sequence is “atomic” in the sense that no other spi_message may use that SPI bus until that sequence completes. On some systems, many such sequences can execute as as single programmed DMA transfer. On all systems, these messages are queued, and might complete after transactions to other devices complete. Messages sent to a given spi_device are always executed in FIFO order.

Below code fragment represents the spi_message system that we have used to READ/WRITE in my function, 'vs10xx_io_ctrl_xf'. First, we register our spi_transfer structure with the spi_message  structure using 'spi_message_init_with_transfers'. We initialize the Tx_Buf & Rx_Buf & their corresponding lengths. After this we add our spi_message to the Tail using 'spi_message_add_tail' & perform a synchronous Receive/Transfer using 'spi_sync' call. The Call just Piggybacks Receive data & it is upto the user to collect the Relevant READ data correctly as I have done in the last part of the function.

int vs10xx_io_ctrl_xf(int id, const char *txbuf, unsigned txlen, char *rxbuf, unsigned rxlen) 
{

    vs10xx_chips[id].transfer[0].tx_buf = vs10xx_chips[id].tx_buf;
    vs10xx_chips[id].transfer[0].len    = sizeof(vs10xx_chips[id].tx_buf);
    vs10xx_chips[id].transfer[1].rx_buf = vs10xx_chips[id].rx_buf;
    vs10xx_chips[id].transfer[1].len    = sizeof(vs10xx_chips[id].rx_buf);

    spi_message_init_with_transfers(&vs10xx_chips[id].msg, vs10xx_chips[id].transfer, ARRAY_SIZE(vs10xx_chips[id].transfer));

    if (txbuf && (txbuf[0] == 0x02)) 
    {
    vs10xx_chips[id].tx_buf[0] = txbuf[0];
    vs10xx_chips[id].tx_buf[1] = txbuf[1];
    vs10xx_chips[id].tx_buf[2] = txbuf[2];
    vs10xx_chips[id].tx_buf[3] = txbuf[3];
    vs10xx_chips[id].transfer[0].len= 0x04;
    spi_message_add_tail(&vs10xx_chips[id].transfer[0], &vs10xx_chips[id].msg);
    status = spi_sync(vs10xx_chips[id].spi_ctrl, &vs10xx_chips[id].msg);

    if (status < 0)
        printk("id:%d spi_sync failed in Tx\n", id);
    }

    if (txbuf && (txbuf[0] == 0x03)) 
    {
       vs10xx_chips[id].tx_buf[0] = txbuf[0];
       vs10xx_chips[id].tx_buf[1] = txbuf[1];
       vs10xx_chips[id].transfer[0].len = 0x02;
    }

    if (rxbuf) 
    {
       vs10xx_chips[id].rx_buf[0] = rxbuf[0];
       vs10xx_chips[id].rx_buf[1] = rxbuf[1];
       status = spi_sync(vs10xx_chips[id].spi_ctrl, &vs10xx_chips[id].msg);

       if (status < 0)
          printk("id:%d spi_sync failed in Rx\n", id);

       rxbuf[0] = vs10xx_chips[id].rx_buf[0];
       rxbuf[1] = vs10xx_chips[id].rx_buf[1];
    }

    return status;
}

Below fragment shows how I am calling SCI READ/WRITE Call to 'vs10xx_io_ctrl_xf' function, which forms the heart of the design of this Linux Device Driver.

/* ----------------------------------------------------------------------------- */
/* VS10XX DEVICE READ/WRITE SCI REGISTERS                  */
/* ----------------------------------------------------------------------------- */

static int vs10xx_device_w_sci_reg(int id, unsigned char reg, unsigned char msb, unsigned char lsb) 
{

    int status = 0;
    unsigned char cmd[] = {0x02, reg, msb, lsb};

    if (status == 0) 
    {
        status = vs10xx_io_ctrl_xf(id, cmd, sizeof(cmd), NULL, 0);
        if (!vs10xx_io_wtready(id, 10)) 
        {
            vs10xx_err("id:%d timeout (reg=%x)", id, reg);
            status = -1;
        }
    }
    return status;
}

static int vs10xx_device_r_sci_reg(int id, unsigned char reg, unsigned char* msb, unsigned char* lsb) {

    int status = 0;

    unsigned char cmd[] = {0x03, reg};
    unsigned char res[] = {0x00, 0x00};

    if (status == 0) 
    {
        status = vs10xx_io_ctrl_xf(id, cmd, sizeof(cmd), res, sizeof(res));
        *msb = res[0];
        *lsb = res[1];
        if (!vs10xx_io_wtready(id, 10)) 
        {

            vs10xx_err("id:%d timeout (reg=%x)", id, reg);
            status = -1;
        }
    }

    return status;
}

Both the READ & WRITE Calls begin with their respective opcodes as we have discussed in their Timing diagrams. This completes our discussion of READ/WRITE of the Audio Codec Registers.



Building the Driver, Toolchain setup, Device Tree & Makefile


In this section, i would discuss about the build & setup of the Device Tree, the Device Driver & the setup required. Firstly, as i said earlier, we would download the Linux Kernel 4.4 sources,

git clone https://github.com/raspberrypi/linux/tree/rpi-4.4.y --branch rpi-4.4.y

Use the following command to download the Cross Compile Toolchain to the home folder:

git clone https://github.com/raspberrypi/tools ~/tools

Updating the $PATH environment variable makes the system aware of file locations needed for cross-compilation. On a 32-bit host system you can update and reload it using:

echo PATH=\$PATH:~/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin >> ~/.bashrc

source ~/.bashrc

If you are on a 64-bit host system, you should use:

echo PATH=\$PATH:~/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin >> ~/.bashrc

source ~/.bashrc

Since, we created & added our own Device Tree for the Audio Codec, we need to add an entry in the Overlay Makefile. We go to the path, /arch/arm/boot/dts/overlays and in the overlays Makefile add the following line,

dtbo-$(RPI_DT_OVERLAYS) += vs10xx.dtbo

After, setting up our Toolchain & getting the Linux Kernel 4.4 sources, now we would set the config & give a Build. For Pi 2, Pi 3, or Compute Module 3:

cd linux

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig


make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs

Now, an important point to reckon here is that, even though we have built the entire Kernel sources, we simply need the Device Tree binaries since we have created & added our Device Tree for VS1053 Audio Codec & that is the only change that we have done with our Kernel sources.

Hence, we don't need to install a fresh Kernel Image but only the Device Tree binary needs to be placed inside the boot partition. This is the power of Device Tree, which doesn't require you to change the Kernel image for any new device we add but only a minor dtb has to be added or updated. This greatly simplifies things from what they were before the advent of Device Tree for the Linux Kernel.

Now, get the Raspbian "Jessie" Image which was based on Linux Kernel 4.4. This one came out during April, 2017. People can download this Raspbian Image from the below link:

http://downloads.raspberrypi.org/raspbian/images/raspbian-2017-04-10/

Download the zip, "2017-04-10-raspbian-jessie.zip", as shown below:




























Flash this Raspbian Image on your microSD Card using Etcher or Win32DiskImager. After, flashing it to your SD Card, plug it to your Rpi3 board & switch on your Rpi3. Type

sudo raspi-config

And from the Advanced options, enable your SPI & Device Tree support.

After enabling SPI & Support device Tree options, we can check in /dev directory by trying the command,

ls -l /dev 

for spidev0.0 & spidev0.1. If, all is fine, then switch off your Rpi3 & plug out your microSD Card & put it in your SD Card Reader to copy the Device Tree Binaries. First, type lsblk command to check the SD Card partitions(sdb1 & sdb2), & if the partitions are mounted, please unmount them first,

lsblk
mkdir /mnt/fat32
chmod 777 /mnt/fat32

mkdir /mnt/ext4
chmod 777 /mnt/ext4

mount /dev/sdb1 /mnt/fat32 -t vfat -o rw
mount /dev/sdb2 /mnt/ext4 -t ext4 -o rw

After, mounting our partitions we copy our dtbo binaries,

cp -rf arch/arm/boot/dts/*.dtb /mnt/fat32/.
cp -rf arch/arm/boot/dts/overlays/*.dtb* /mnt/fat32/overlays/.

This successfully copies our dtb binaries respectively.
Now, unmount your microSD Card,

umount /mnt/fat32 /mnt/ext4

The Makefile of our Linux Device Driver would be something like this,

obj-m +=  vs10xx.o

PWD   := $(shell pwd)
all:
@$(MAKE) ARCH=arm CROSS_COMPILE=${CCPREFIX} -C /home/r/linux M=$(PWD) modules
clean:
make ARCH=arm CROSS_COMPILE=${CCPREFIX} -C /home/r/linux M=$(PWD) clean


Now, to enable our Device Tree during bootup time, we will need to make an entry in /boot/config.txt file, as shown below:

dtoverlay=vs10xx:spi0-0-present,spi0-0-speed=1000000,spi0-1-present,spi0-1-speed=4000000

Before, I close this section I would like people to look into these following VS1053 Audio Codec Registers which hold significance to us,


























Section 8.7 of the Datasheet lists the SCI Register set of the Audio Codec.
The SCI_MODE Control is one of the important registers to start with. The Register Address is 0x00. After, Reset if we READ this register, the READ value shall be 0x0800.







































When SM CANCEL is Set & the Set is detected by a codec, it will stop decoding and return to the main loop. The stream buffer content is discarded and the SM CANCEL bit cleared. Similarly SM_SDIORD will set the SDI Data order as MSB or LSB for the Audio Codec.SM_LINE1 will select either the Microphone Input or Line-In AUX Input for Recording or Audio Capture. For Audio Playback it can be kept 0. The SM_CLK Range should be set based on the External oscillator frequency. Anything lower than this frequency may not make the Audio Codec work.



















The only important field that is of our interest here is the SS_VER field. SS VER is 0 for VS1001, 1 for VS1011, 2 for VS1002, 3 for VS1003, 4 for VS1053, 5 for VS1033, and 7 for VS1103. Hence, READ this field to ensure correctness of SPI Communication. I was able to READ this field as 3, which means my Codec was having VS-1003 Audio Codec.
















The Bass Enhancer VSBE is a powerful bass boosting DSP algorithm, which tries to take the most out of the users earphones without causing clipping.

VSBE is activated when SB AMPLITUDE is non-zero. SB AMPLITUDE should be set to the user’s preferences, and SB FREQLIMIT to roughly 1.5 times the lowest frequency the user’s audio system can reproduce. For example setting SCI BASS to 0x00f6 will have 15 dB enhancement below 60 Hz.

Note: Because VSBE tries to avoid clipping, it gives the best bass boost with dynamical music material, or when the playback volume is not set to maximum. It also does not create bass: the source material must have some bass to begin with.

Treble Control VSTC is activated when ST AMPLITUDE is non-zero. For example setting SCI BASS to 0x7a00 will have 10.5 dB treble enhancement at and above 10 kHz.Bass Enhancer uses about 2.1 MIPS and Treble Control 1.2 MIPS at 44100 Hz samplerate. Both can be on simultaneously.











































Since we are using our external oscillator as 12.288 Mhz, hence no need to change SC_FREQ. We set SC_MULT as 0xC000 which means XTALI x 4.5.

The SCI_AUDATA must be set to correct Sampling Rate of 0xAC45 for 44100 sampling rate.




WRITE 0xC017 with value 0x03 & WRITE 0xC019 with value 0x00. This will make GPIO 0 & GPIO 1 as Output Pins & Pull them LOW & switch Mode to Mp3 Playback Mode.

The complete Audio Pipeline of VS1053/VS1003 Audio Codec is given as below:


This completes the discussion of the development of a Linux Device Driver for Audio Codec VS1053/VS1003. The next few sections would be the Resource section. Please write to me in case of queries. My Email is: rajiv.biswas55@gmail.com

I have also made a few videos on You Tube.






.......






Resources Section



-   To download the Driver Sources please use GIT:




git clone https://github.com/RajivBiswas/RaspberryPi-Dev/tree/master/vs10xx-driver

To Build the Project first setup the Toolchain as i have mentioned in my above Blog Page:

Please refer the section, "Building the Driver, Toolchain setup, Device Tree & Makefile".

Please ensure to change the Makefile, the Path where you extracted your Linux Kernel source.
Mine was at /home/r/linux

vs10xx-objs := vs10xx_iocomm.o vs10xx_queue.o vs10xx_device.o vs10xx_main.o

obj-m +=  vs10xx.o
PWD   := $(shell pwd)
all:
@$(MAKE) ARCH=arm CROSS_COMPILE=${CCPREFIX} -C /home/r/linux M=$(PWD) modules
clean:
make ARCH=arm CROSS_COMPILE=${CCPREFIX} -C /home/r/linux M=$(PWD) clean

After Building the Kernel sources, modules & DTs successfully, go inside the sources
of this Linux Driver & type:

make clean
make all

After successful build, copy the entire folder to your Rpi3 Home path using SD Card Reader.
After this power on your Rpi3 & go to the directory path of this Linux Device Driver.
Insert the Module using insmod

insmod vs10xx.ko

Check the dmesg logs

dmesg

The dmesg logs should show correct VERSION string for your Audio Codec, be it VS1003/VS1053/VS1063 et al.

Check in \dev directory. There must be an entry by \dev\vs10xx-0 after successful insmod.

-   To download the User Space IOCTL Program, use the command



git clone https://github.com/RajivBiswas/RaspberryPi-Dev/tree/master/vs10xx_userspace_ioctl

Please create a new directory on your Rpi3 Home path.
Copy the sources of the directory to the new directory.
After, this on your Rpi3 Board, inside the sources of this folder, type,

make clean
make all

This will build the User space program for you on your Rpi3. To execute the program on your
Rpi3, type,

./ioctl --help
This will list out all the options provided with the Program.

"  reset\n"
"  getscireg    regno\n"
"  setscireg    regno msb lsb\n"
"  getclockf\n"
"  setclockf    mul add clk\n"
"  getvolume\n"
"  setvolume    left right\n"
"  gettone\n"
"  settone      tb tl bb bl\n"
"  getinfo\n"
"  play\n\n"

First, the User shall change the volume using,

./ioctl getvolume
./ioctl setvolume 220 220
./ioctl getvolume

First 'getvolume' shall show 255, 255 as Left & Right channel Volume level values.
Upon setting the volume levels as 220 & 220 for Left & Right channels & then Reading
them using 'getvolume' command should successfully Read the volume levels as 220 & 220
for Left & Right channel values. This is also a way to ensure correctness of our Linux
Device Driver.

Please enusre that after 'insmod vs10xx.ko' there is a device node for vs10xx in /dev
directory. By default the User space program assumes it to be in /dev/vs10xx-0.

If anything other than this is there, then the above command can be also given as,

./ioctl -d /dev/vs10xx-0 getvolume
./ioctl -d /dev/vs10xx-0 setvolume 220 220
./ioctl -d /dev/vs10xx-0 getvolume

After, setting the Volume level, we can play an Mp3 file encoded at 128bps, 44100 Hz
Sampling Rate, Stereo, Layer 3 Mp3 file as,

./ioctl play

User needs to change the Path of the Audio file. Currently, i have hard coded it in
ioctl.c as,

char filename[] = "/home/pi/Music/NinjaTuna128k.mp3";

Also, find the file NinjaTuna128k.mp3 present in this same git folder.

Some important FFMPEG Commands to create 128kbps, 44100 Hz stereo Mp3 files is as
given below:

To convert 192kbps Mp3 to 128kbps Mp3:
ffmpeg -i NinjaTuna.mp3 -b:a 128k -f mp3 NinjaTuna128k.mp3

To convert WAV to 128kbps Mp3:
ffmpeg -i input.wav -vn -ar 44100 -ac 2 -ab 192k -f mp3 output.mp3

-   To download the DTS Overlay for VS10xx 



git clone https://github.com/RajivBiswas/RaspberryPi-Dev/tree/master/vs10xx-overlay


This completes everything i developed on this Project so far. 
Further suggestions & queries can be put forth in the Comments section.

Regards,
Rajiv.















7 comments:

  1. At the moment I have interpreted the command:
    git clone https://github.com/raspberrypi/linux/tree/rpi-4.4.y --branch rpi-4.4.y
    as follows
    git clone https://github.com/raspberrypi/linux/tree/rpi-4.9.y --branch rpi-4.9.y
    Is it a proper change?
    Thank you and best regards, Sergio

    ReplyDelete
    Replies
    1. What is the Raspbian/ Debian Distribution you are using ? If your Raspbian Debian distribution is using Linux Kernel 4.9, then clone the kernel sources for 4.9 and build the driver sources against this kernel source...please refer the Makefile of my Driver in "Resources section"..The Kernel object that is generated when you build a Kernel driver is linked to the kernel source of that version..hence if you have built your driver for Kernel 4.4, but your Raspbian Debian distribution is based out of Kernel 4.9, then an insmod or modprobe would fail as the kernel version of the driver is different...I was using a Raspbian /Debian distribution that was released in April-May, 2017 having kernel version 4.4, hence downloaded 4.4 kernel sources, developed my driver and built the driver linking it to the 4.4 downloaded kernel sources...the generated kernel object when insmod, gave me no error on my Rpi board as both were the same versions...

      Delete
  2. Hi.
    I have little experience, I have purchased my RPi less than 1 month ago.
    I have tested ALSA driver and lame MP3 codec to encode an audio stream.
    Lame codec works fine to me therefore I'd like to hear from you why you have added a daughter board to RPi?
    Did you find Lame and it was not fulfilling your needs?
    Many thanks and best regards, s.

    ReplyDelete
    Replies
    1. The VS1053 is a Hardware Audio Codec chip...so you can decode your Mp3 Audio files and play the decoded stream using this decoder...Lame Codec is a Software decoder program for decoding Mp3 Audio files..The Software program executes on your RaspberryPi3 or RaspberryPi zero and consumes the processing MIPS of the ARM Cortex processor...thus a considerable overhead load is wasted in this audio decoding algorithm processing by Rpi3 around 30-40% given the high Audio encoding bit rates by Mp3 files...by using a Hardware Audio codec, you transfer this task to be done by the Hardware Audio Codec which saves a considerable processing load on your Rpi3...and thus saves a lot of processor MIPS or cycles of execution which can be used in controlling other tasks...

      Delete
  3. Hi,
    after having read as best I could your precious post
    https://blogsmayan.blogspot.it/p/linux-device-driver.html
    I have some doubts that I'd like to submit to you regarding to disk quota
    I have a 16GB sd card used by about 4GB and now
    pi@raspberrypi:~ $ df -h
    Filesystem Size Used Avail Use% Mounted on
    /dev/root 15G 7.0G 6.6G 52% /
    Checking different disk areas returned as follows (all measueres are roughly):
    - 1.1GB in /home/pi/linux
    - 1,5GB in /home/pi/tools

    Minor notes:
    - I reduced /usr/share to 1GB after Synaptic PM installation and texlive-doc folder purging
    - I've also clean /var/cache (from 1.4GB to 0.05) and the trash

    The two folders "linux" and "tools" are related to the two clones:
    - git clone https://github.com/raspberrypi/linux/tree/rpi-4.4.y --branch rpi-4.4.y (I have actually run the command: git clone https://github.com/raspberrypi/linux.git --branch rpi-4.14.y)
    - git clone https://github.com/raspberrypi/tools ~/tools

    That said, my question is: after the Build (the two "make" command lines) and all device tree blob files transfer, those two folders can be purged?
    Many thanks.

    ReplyDelete
  4. Hi, the VS1053b has capability to encode Ogg Vorbis audio. Is it possible to stream audio to the Raspi using the VS1053?

    ReplyDelete
  5. good evening my name is tedy from indonesia i have read your article regarding the vs1053 audio codec module used on raspberry, very special, but i have a black beaglebone how can vs 1053 be used on beaglebone black can you make a debian linux os with drivers installed vs1053 for Beaglebone I have high hopes for that thank you for your attention I respect tedy

    ReplyDelete