AM62L: AM62L ADC Support in U-Boot and Linux Kernel

Part Number: AM62L

Dear TI Support Team,

We are working on a custom board based on the TI AM62L and require ADC support for board/SOM revision detection.

We would like to clarify the following:

U-Boot ADC Support

Is there an officially supported AM62L ADC driver in U-Boot?
If yes, could you provide the corresponding driver, API, configuration, and Device Tree example?
If there is no AM62L-specific ADC driver in U-Boot, is there a TI reference implementation or recommended approach to add ADC support?

Linux Kernel IIO ADC Support

What is the recommended Linux kernel IIO ADC driver/API for reading the AM62L internal ADC?

Which IIO APIs should be used to:
Obtain an ADC channel
Read the raw ADC value
Read the processed/voltage value, if required

We would appreciate any relevant TI documentation, source code, patches, or reference examples.

Thanks and Regards,
Kumareshan

  • Hello Kumareshan,

    Apologies for the delay. I've re-assigned the thread to the subject matter expert. Please allow some time for a response.

  • Hi Kumareshan,
    U-Boot currently does not have ADC driver support, may I know the usecase behind this requirement?

    For Linux kernel ADC usage,
    Please refer: https://software-dl.ti.com/processor-sdk-linux/esd/AM64X/12_01_00_05_03/exports/docs/linux/Foundational_Components/Kernel/Kernel_Drivers/ADC.html 

    Do note, that ADC driver in Linux kernel are there in the default dts, above guide on AM64 (which also has internal ADC) will help you use the same on AM62L as well.

  • Hi Divyansh,

    Our use case is to read the AM62L SOM PCB and BOM revisions using the available ADC channels in U-Boot and print the detected SOM revision during the U-Boot boot process.

    In the kernel, we attempted to read the AM62L SOM PCB/BOM revision using the available ADC channels with the source code provided below. However, the implementation is currently not working as expected.


    #ifdef CONFIG_IG69M
    /* iWave board revision is combination of  pcb revsion and bom revision */
    struct ig_board_rev_nr {
        char pcb_rev;
        char bom_rev;
    };
    
    unsigned int data;
    int channel = 0, ret = 0;
    
    static int ig_get_bom_revision(struct ig_board_rev_nr *board_rev,
                                   unsigned int raw, int bom_rev_flag)
    {
            if (!bom_rev_flag)
            {
                    switch (raw) {
                            case 0 ... 100:
                                    board_rev->bom_rev = '0';
                                    break;
                            case 300 ... 450:
                                    board_rev->bom_rev = '1';
                                    break;
                            case 600 ... 850:
                                    board_rev->bom_rev = '2';
                                    break;
                            case 1150 ... 1450:
                                    board_rev->bom_rev = '3';
                                    break;
                            case 1500 ... 1800:
                                    board_rev->bom_rev = '4';
                                    break;
                            case 1900 ... 2200:
                                    board_rev->bom_rev = '5';
                                    break;
                            case 2300 ... 2600:
                                    board_rev->bom_rev = '6';
                                    break;
                            case 2650 ... 2950:
                                    board_rev->bom_rev = '7';
                                    break;
                            case 3200 ... 3450:
                                    board_rev->bom_rev = '8';
                                    break;
                            case 3500 ... 3650:
                                    board_rev->bom_rev = '9';
                                    break;
                            case 4000 ... 4150:
                                    board_rev->bom_rev = 'A';
                                    break;
                            default:
                                    board_rev->bom_rev = 'x';
                    }
            }
            else
            {
                    switch (raw) {
                            case 0 ... 100:
                                    board_rev->bom_rev = '0';
                                    break;
                            case 300 ... 450:
                                    board_rev->bom_rev = '1';
                                    break;
                            case 600 ... 850:
                                    board_rev->bom_rev = '2';
                                    break;
                            case 1150 ... 1450:
                                    board_rev->bom_rev = '3';
                                    break;
                            case 1500 ... 1800:
                                    board_rev->bom_rev = '4';
                                    break;
                            case 1900 ... 2200:
                                    board_rev->bom_rev = '5';
                                    break;
                            case 2300 ... 2600:
                                    board_rev->bom_rev = '6';
                                    break;
                            case 2650 ... 2950:
                                    board_rev->bom_rev = '7';
                                    break;
                            case 3200 ... 3450:
                                    board_rev->bom_rev = '8';
                                    break;
                            case 3500 ... 3650:
                                    board_rev->bom_rev = '9';
                                    break;
                            case 4000 ... 4150:
                                    board_rev->bom_rev = 'A';
                                    break;
                            default:
                                    board_rev->bom_rev = 'x';
                    }
            }
    
            return 0;
    }
    
    /* Read the adc value based on the dts pin configuration */
    static int adc_measurement(struct device_node *node, int adc_count,
                               struct ig_board_rev_nr *board_rev)
    {
            struct of_phandle_args adc_args;
            struct device *adc_dev;
            struct iio_dev *indio_dev;
            struct iio_channel chan;
            unsigned int raw;
            int ret, uV;
            int i;
            int index;
            int bom_rev_flag = 0;
    
            for (i = 0; i < adc_count; i++) {
                    ret = of_parse_phandle_with_args(node, "ig69m-adc",
                                                     "#io-channel-cells", i,
                                                     &adc_args);
                    if (ret) {
                            pr_err("can't find /config/ig,adc_som_rev\n");
                            return 0;
                    }
    
                    /* Resolve the phandle's device_node to its iio_dev.
                     * "ig69m-adc" is a custom property name (not the
                     * standard "io-channels"/"io-channel-names" pair), so
                     * the public fwnode_iio_channel_get_by_name()/
                     * iio_channel_get() consumer APIs cannot be used here -
                     * neither accepts an arbitrary property name + index.
                     * bus_find_device_by_of_node() + dev_to_iio_dev() is
                     * the same resolution path IIO's own (non-exported)
                     * of_iio_channel_get() uses internally. */
                    adc_dev = bus_find_device_by_of_node(&iio_bus_type,
                                                         adc_args.np);
                    of_node_put(adc_args.np);
    
                    if (!adc_dev) {
                            pr_err("Can't get adc device for channel %d\n", i);
                            return -ENODEV;
                    }
    
                    indio_dev = dev_to_iio_dev(adc_dev);
    
                    index = adc_args.args_count ? adc_args.args[0] : 0;
                    if (index >= indio_dev->num_channels) {
                            pr_err("ADC channel index %d out of range for %s\n",
                                   index, dev_name(adc_dev));
                            put_device(adc_dev);
                            return -EINVAL;
                    }
    
                    chan.indio_dev = indio_dev;
                    chan.channel = &indio_dev->channels[index];
                    chan.data = NULL;
    
                    ret = iio_read_channel_raw(&chan, (int *)&raw);
                    if (ret) {
                            pr_err("single shot failed for ADC[%d]!\n", index);
                            put_device(adc_dev);
                            return ret;
                    }
    
                    /* Convert to uV */
                    ret = iio_read_channel_processed(&chan, &uV);
    
                    if (ret >= 0) {
                            if (i == 0)
                            {
                                    switch (raw) {
                                            case 0 ... 100:
                                                    board_rev->pcb_rev = '1';
                                                    bom_rev_flag = 0;
                                                    break;
                                            case 300 ... 450:
                                                    board_rev->pcb_rev = '1';
                                                    bom_rev_flag = 1;
                                                    break;
                                            case 600 ... 850:
                                                    board_rev->pcb_rev = '2';
                                                    bom_rev_flag = 0;
                                                    break;
                                            case 1150 ... 1450:
                                                    board_rev->pcb_rev = '2';
                                                    bom_rev_flag = 1;
                                                    break;
                                            case 1500 ... 1800:
                                                    board_rev->pcb_rev = '3';
                                                    bom_rev_flag = 0;
                                                    break;
                                            case 1900 ... 2200:
                                                    board_rev->pcb_rev = '3';
                                                    bom_rev_flag = 1;
                                                    break;
                                            case 2300 ... 2600:
                                                    board_rev->pcb_rev = '4';
                                                    bom_rev_flag = 0;
                                                    break;
                                            case 2650 ... 2950:
                                                    board_rev->pcb_rev = '4';
                                                    bom_rev_flag = 1;
                                                    break;
                                            case 3200 ... 3450:
                                                    board_rev->pcb_rev = '5';
                                                    bom_rev_flag = 0;
                                                    break;
                                            case 3500 ... 3650:
                                                    board_rev->pcb_rev = '5';
                                                    bom_rev_flag = 1;
                                                    break;
                                            case 3950 ... 4150:
                                                    board_rev->pcb_rev = '6';
                                                    bom_rev_flag = 0;
                                                    break;
                                            default:
                                                    board_rev->pcb_rev = 'x';
                                    }
                            }
                            else {
                                    ig_get_bom_revision(board_rev, raw,
                                                        bom_rev_flag);
                            }
                    }
                    else {
                            pr_err("Can't get uV value for ADC[%d]\n", index);
                    }
    
                    put_device(adc_dev);
            }
    
            return 0;
    }
    
    static int board_get_som_revision(struct ig_board_rev_nr *board_rev)
    {
            struct device_node *node;
            int adc_count, ret;
    
            node = of_find_node_by_path("/config");
            if (!node) {
                    pr_err("no /config node?\n");
                    return -ENOENT;
            }
    
            /*
             * Retrieve the ADC channels devices and get measurement
             * for each of them
             */
            adc_count = of_count_phandle_with_args(node,
                                           "ig69m-adc",
                                           "#io-channel-cells");
            if (adc_count < 0) {
                    of_node_put(node);
                    if (adc_count == -ENOENT)
                            return 0;
    
                    pr_err("Can't find adc channel (%d)\n", adc_count);
    
                    return adc_count;
            }
    
            ret = adc_measurement(node, adc_count, board_rev);
            of_node_put(node);
    
            if (ret)
                    return ret;
    
            return 0;
    }
    
    static void print_board_info (void)
    {
            struct ig_board_rev_nr *board_rev1;
            int ret;
    
            board_rev1 = kzalloc(sizeof(struct ig_board_rev_nr), GFP_KERNEL);
            if (!board_rev1) {
                    pr_err("Failed to allocate board_rev\n");
                    return;
            }
    
            /* IG69M: Get board revision */
            ret = board_get_som_revision(board_rev1);
            if (ret) {
                    pr_err("Failed to read SOM revision (%d)\n", ret);
                    kfree(board_rev1);
                    return;
            }
    
            pr_info("\nBoard Info:\n");
            pr_info("\tSOM Version       : iG-PRJRZ-AP-01-R%c.%c\n",
                    board_rev1->pcb_rev, board_rev1->bom_rev);
    
            kfree(board_rev1);
    }
    #endif
    
    

    Thanks and Regards,
    Kumareshan

  • Hi Kumareshan,
    We currently do not have any example for U-Boot ADC, but since AM62L ADC uses AM335 ADC compatible in kernel, and the same driver exists in U-Boot, you may try defining the same in U-Boot as well.

    For kernel, let me know if you are facing any difficulty using the AM64 resources I shared. This is one of the ways you can use it: https://github.com/TexasInstruments/lvgl/blob/96798d5cec1b4356e44a72d6900b5f29e861cc40/demos/high_res/adc.c 

  • Dear Divyansh,

    I have checked the U-Boot source, and the TI ADC driver required for the AM62L is currently not available.

    The available ADC-related drivers in our U-Boot source are:

    adc-uclass.c
    adc-uclass.o
    exynos-adc.c
    imx93-adc.c
    Kconfig
    Makefile
    meson-saradc.c
    rockchip-saradc.c
    sandbox.c
    stm32-adc.c
    stm32-adc-core.c
    stm32-adc-core.h

    The U-Boot ADC framework (adc-uclass.c) is available, but there is no TI/AM62L TSCADC provider driver. Therefore, adc_channel_single_shot() cannot access the AM62L ADC device and currently returns -ENODEV.

    Since the Linux kernel already has the TI ADC driver for AM62L, could we reuse or port the existing kernel ADC driver implementation to U-Boot? Alternatively, please advise if there is an existing TI AM62L U-Boot ADC driver or recommended approach for reading the ADC channels in U-Boot.

    Our requirement is to read the AM62L SOM PCB/BOM revision using ADC channels and print the SOM revision in U-Boot.

  • Hi Kumaresan,
    Apologies for the confusion. You are right, there is no reference TI ADC implemenetation in U-Boot yet, and you'll need to port it from the Linux driver.