Skip to content

GX8002 linux Driver migration Description*

1. This section describes the solution scenarios*

GX8002 As a low-power, ultra-small AI chip, it can meet the offline wake-up function of wearing battery products.

For example: on the learning tablet, when the tablet enters the hibernation state, only GX8002 can continue to work. Once GX8002 voice wake up, use GPIO to interrupt wake up the tablet master. If necessary, it can also obtain the 2-second cache audio of wake up through the SPI interface of GX8002. The tablet will do the 2-level wake up confirmation to avoid false wake up.

GX8002 support empty piece OTA upgrade, The interface supports UART and I2C, which is convenient for hardware selection.

The chip information of GX8002 is as follows:

  • The SOC is the tablet's main CPU. The two share an analog microphone, can each take audio data, do not interfere.
  • Interrupt wake up SOC via GPIO. The interrupt pulse length can be customized.
  • SPI is used to quickly transmit 2 seconds of wake up audio, which meets the requirement of SOC's 2 level wake up confirmation.
  • I2C is used for OTA upgrade function and factory test function of GX8002, and can also customize some functions requiring mutual interaction.
  • After SOC wakes up, the power supply of GX8002 can be cut off through GPIO to reduce power consumption when working. When the SOC goes to sleep, start the GPIO power supply in advance to make the GX8002 work. GX8002 Boot in the millisecond level, the speed is very fast, less than 100 milliseconds.

3. Driver migration guide*

3.1 GX8002 I2C*

Interactive use of GX8002 I2C0 this group. See the chip diagram in Section 1 for details.

The 7-bit address of the device is: 0x2F. Some platform drivers need to move one bit to the left: 0x5E.

For example, get the wake source:

After waking up, read one byte into the 0xA0 register, reading 0x64 represents "小x小x" wake up event. This is mainly used when there are more than one wakeword, to identify which wakeword is triggered.

We will provide linux4.4 verified driver code, which already contains all the details of I2C interaction, customers just need to port over. Driver source code includes:

  • Get wake source

  • OTA upgrade

  • Obtain the firmware version

  • Plant test interaction

3.2 GX8002 SPI*

SPI is used to transfer 2 seconds of audio data from the GX8002 cache in the format of 16K 16bit PCM audio.

The SPI speed of GX8002 is up to 4M, and 3M is recommended.

GX8002 SPI transmission format msb, using spi mode 0.

GX8002 SPI can master and slave. In this scheme, GX8002 is recommended as the slave device.

GX8002 wakes up, first sends a pulse to GPIO0, and then actively sends 16K ADPCM audio through SPI.

After the Linux driver recognizes the wake up interrupt, it receives audio data in ADPCM format through SPI. ADPCM is decoded into PCM 16K 16bit 64K raw audio data for tablet application layer to obtain through linux standard read interface.

3.3 Driver code migration description*

Decompress the gxcodec_8002.zip file. The device tree file is my.dts. The readme.txt file contains the compilation instructions. The code in the demo directory is a complete example of how to interact with GX8002 on the linux application layer through device files.

Tests on linux4.4 have so far passed. gxcodec_8002.zip (http://yun.nationalchip.com:10000/l/9FrkOc)

  • readme.txt】 Compile specification file
  • demo】 Application layer to use the reference routine, open: /dev/gxcodec for the corresponding read and ioctl operation
  • gxscpu_boot.hGX8002 The bootloader binary used by OTA
  • gxcodec_main.c】Driver master code
  • gxcodec_i2c.c】Drive I2C code, including interaction details
  • gxcodec_spi.c】Drives SPI code that contains interaction details
  • gxcodec_upgrade.cGX8002 OTA I2C upgrade protocol code
  • adpcm.c】adpcm audio decoding

As long as customers modify the device tree file of the linux kernel appropriately and adapt to the driver, the purpose of transplantation can be achieved.

For example, the customer wants to modify their own I2C interface, GPIO interface and SPI interface.

The driver main function entry is: gxcodec_main.c, which registers a platform device:

/* Declare a list of devices supported by the driver */
static const struct of_device_id gxcodec_of_match[] = {
    { .compatible = "nationalchip,gxcodec", 0},
    {}
};

MODULE_DEVICE_TABLE(of, gxcodec_of_match);

/* Define platform driver structure */
static struct platform_driver gxcodec_platform_driver = {
    .probe = gxcodec_platform_probe,
    .remove = gxcodec_platform_remove,
    .driver = {
        .name = "gxcodec",
        .of_match_table = gxcodec_of_match,
        .owner = THIS_MODULE,
    }
};

/* Register our platform driver */
module_platform_driver(gxcodec_platform_driver);

Device tree reference:

wakeup-gpio is a wakeup gpio, selected according to the actual hardware of the master

power-GPIO is a gpio used by the main control to power the GX8002. Select the GPIO based on the actual hardware of the main control

gxcodec {
    compatible = "nationalchip,gxcodec";
    wakeup-gpio = <0x37 0x17 0x00>;
    power-gpio = <0x38 0x01 0x00>;
};

In the probe of platform device, one GPIO interrupt wake source, one power supply GPIO, one I2C driver, one SPI driver and one miscellaneous device are registered successively, and a kernel task of recording cache is added.

/* Add probe() function */
static int __init gxcodec_platform_probe(struct platform_device *pdev)
{
    int ret_val, irq;

    pr_info("gxcodec_platform_probe() function is called.\n");
    pr_info("gxcodec driver version - %s\n", DRIVER_VERSION);

    adpcm_buf = devm_kmalloc(&pdev->dev, ENCODEC_DATA_PACKET_SIZE+PCM_VERIFICATION_FLAG_SIZE, GFP_KERNEL | __GFP_REPEAT);
    if (!adpcm_buf) {
        pr_err("Can't allocate param buffer (size = %d)!\n", ENCODEC_DATA_PACKET_SIZE+PCM_VERIFICATION_FLAG_SIZE);
        return -1;
    }
    pcm_buf = devm_kmalloc(&pdev->dev, PCM_BUFFER_SIZE, GFP_KERNEL | __GFP_REPEAT);
    if (!pcm_buf) {
        pr_err("Can't allocate param buffer (size = %d)!\n", PCM_BUFFER_SIZE);
        return -1;
    }

    mutex_init(&p_lock); // A lock that includes readings of cached audio

    ret_val = misc_register(&gx_miscdevice); // Miscellaneous device driver registration

    if (ret_val != 0) {
        pr_err("could not register the misc device mydev\n");
        return ret_val;
    }

    pr_info("mydev: got minor %i\n",gx_miscdevice.minor);

    gxcodec_i2c_init(); // I2C driver

    gxcodec_spi_init(); // SPI driver

    // reset chip
    power_gpio_desc = devm_gpiod_get(&pdev->dev, "power", GPIOD_OUT_LOW);
    if (IS_ERR(power_gpio_desc)) {
        pr_err("failed to gpiod get.\n");
        return -1;
    }
    reboot_chip();

    // Initializes the recording task
    INIT_WORK(&sv_work, gx_sv_work);

    // Wake up interrupt source registration
    wakeup_gpio_desc = devm_gpiod_get(&pdev->dev, "wakeup", GPIOD_IN);
    if (IS_ERR(wakeup_gpio_desc)) {
        pr_err("failed to gpiod get.\n");
        return -1;
    }

    irq = gpiod_to_irq(wakeup_gpio_desc);
    if (irq < 0) {
        pr_err("%s get irq error.\n", pdev->name);
    }

    pr_info("%d %s get irq.\n", irq, pdev->name);

    ret_val = devm_request_irq(&pdev->dev, irq, active_irq,IRQF_TRIGGER_FALLING, "active", pdev);
    if (ret_val) {
        pr_err("failed to devm_request_irq irq error:%d\n", ret_val);
    }

    return 0;
}

I2C device tree Reference :

gxcodec-i2c@2f {
                compatible = "nationalchip,gxcodec-i2c";
                reg = <0x2f>;
            };

SPI's Device tree reference :

gxcodec-spi@0 {
                compatible = "nationalchip,gxcodec-spi";
                reg = <0x00>;
                spi-max-frequency = <3000000>;
                status = "okay";
            };

The _misc_dev_ioctl of gxcodec_main.c is a core function, including I2C interaction, through CMD to distinguish different commands, interaction, including: get wake up message, get firmware version number, firmware upgrade.

static long misc_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int res;
    pr_info("misc_dev_ioctl() is called. cmd = %x, arg = %ld\n", cmd, arg);

    switch(cmd) {
    case GET_WAKEUP_ID:
    {
        int event_id = i2c_get_event_id();
        res = copy_to_user((void *)arg, (void *)&event_id, sizeof(event_id));
        break;
    }

    case GET_FW_VERSION:
    {
        int firmware_version = i2c_get_firmware_version();
        res = copy_to_user((void *)arg, (void *)&firmware_version, sizeof(firmware_version));
        break;
    }

    case GET_MIC_STATUS:
    {
        char mic_status = i2c_get_mic_status();
        res = copy_to_user((void *)arg, (void *)&mic_status, sizeof(mic_status));
        break;
    }

    case SET_CHIP_POWER:
    {
        i2c_set_power_status(arg);
        res = 0;
        break;
    }

    case UPGRADE_FIRMWARE:
    {
        if (copy_from_user(&img_info_t, (void *)arg, sizeof(img_info_t))) {
            pr_err("copy_from_user failed!\n");
            return -1;
        }
        pr_info("D:%p, L:%d\n", img_info_t.data, img_info_t.size);
        unsigned char *firmware = kmalloc(img_info_t.size, GFP_KERNEL | __GFP_REPEAT);
        if (!firmware) {
            pr_err("Can't allocate param buffer (size = %d)!\n", img_info_t.size);
            return -1;
        }
        if (copy_from_user(firmware, img_info_t.data, img_info_t.size)) {
            pr_err("copy_from_user failed!\n");
            return -1;
        }

        res = gxcodec_upgrade_firmware(reboot_chip, firmware, img_info_t.size);
        kfree(firmware);
        break;
    }

    default:
        pr_info("invalid cmd\n");
        break;
    }

    return res;
}

misc_dev_read Used to get 2 seconds of pcm audio from the driver cache.

misc_dev_read is a timeout read function. If no data is read inside, it will exit after the timeout seconds. The read length is 0.

static ssize_t misc_dev_read(struct file *file, char __user *buf, size_t count_want, loff_t *f_pos)
{
    int ret = 0;
    int count = 100;

    pr_info("misc_dev_read() is called\n");

    if (count_want != PCM_BUFFER_SIZE)
    {
        pr_info("count_want must be 1024*64 bytes\n");
        return 0;
    }

    while(count > 0)
    {
        count--;
        mutex_lock(&p_lock);

        if (buffering == 0)
        {
            mutex_unlock(&p_lock);
            msleep(10);
        }
        else if (buffering == 1)
        {
            goto END;
        }
    }

    pr_info("read time out\n");
    return 0;

END:
    if (copy_to_user((void *)buf, (void *)pcm_buf, PCM_BUFFER_SIZE))
    {
        ret = 0;
    }
    else
    {
        ret = PCM_BUFFER_SIZE;
        buffering = 0;
    }
    mutex_unlock(&p_lock);

    return ret;
}

3.4 ioctl cmd description*

Currently supports 5 cmd, refer to demo.c.

  • \#define GET_WAKEUP_ID 0x0800 //Get wake up event
    After the wake up interrupt is triggered, the wake up event id is obtained. The wake up event id of xiaobuxiaobu =100

  • \#define GET_FW_VERSION 0x0801 //Obtain the firmware version
    The version number consists of four bytes, such as: v0.0.0.1

  • \#define GET_MIC_STATUS 0x0802 //Obtain mic status for testing mic
    In this function, the chip vad function is used to test whether the mic link is up. In the case of external tone, the value 1 is normal and 0 is abnormal

  • \#define SET_CHIP_POWER 0x0803 //Set the power supply for the chip

    • Define macros if power is controlled using a general purpose gpio#define CHIP_POWER_BY_GPIO
      Configure the device tree based on actual hardware conditions: power-gpio
      Code setup CHIP_POWER_ENABLE_SET_VALUE 01

    • Annotate macros if PMU or otherwise is used#define CHIP_POWER_BY_GPIO

    • Additional interface in the driver

      void enable_chip(void)
      void disable_chip(void)
      void reboo_chip(void)
      
  • \#define UPGRADE_FIRMWARE 0x0888 //Upgrade firmware
    You need to configure macros based on the hardware requirements #define GXCODEC_I2C_DATA_ADDRESS 0x36 //boot i2c addr, decision: GPIO2 low->0x35, GPIO2 high->0x36

    When the chip is powered on, the gpio level will be detected. The address is 0x35 on low power and 0x36 on high power