ADC

来自Firefly wiki
跳转至: 导航搜索

Introduction

Firefly-RK3288 development board has three types of AD interfaces: High-speed ADC Stream Interface, Temperature Sensor and Successive Approximation Register. This article will introduce how to configure these ADCs to work properly.

Linux kernel controls the ADC via industrial I/O subsystem, which is intended to provide support for ADC or DAC devices.

The related data structure and configuration are shown as below.

Data Structure

struct iio_channel

struct iio_channel {
    struct iio_dev *indio_dev;// Device on which the channel exists
    const struct iio_chan_spec *channel;//Full description of the channel
    void *data;// Data about the channel used by consumer
};

struct iio_dev

This structure mainly describes the industrial I/O device, as defined below:

struct iio_dev {
    int             id;
    int             modes;
    int             currentmode;
    struct device           dev;
    struct iio_event_interface  *event_interface;
    struct iio_buffer       *buffer;
    struct list_head        buffer_list;
    int             scan_bytes;
    struct mutex            mlock;
    const unsigned long     *available_scan_masks;
    unsigned            masklength;
    const unsigned long     *active_scan_mask;
    bool                scan_timestamp;
    unsigned            scan_index_timestamp;
    struct iio_trigger      *trig;
    struct iio_poll_func        *pollfunc;
    struct iio_chan_spec const  *channels;
    int             num_channels;
    struct list_head        channel_attr_list;
    struct attribute_group      chan_attr_group;
    const char          *name;
    const struct iio_info       *info;
    struct mutex            info_exist_lock;
    const struct iio_buffer_setup_ops   *setup_ops;
    struct cdev         chrdev;
#define IIO_MAX_GROUPS 6
    const struct attribute_group    *groups[IIO_MAX_GROUPS + 1];
    int             groupcounter;
    unsigned long           flags;
#if defined(CONFIG_DEBUG_FS)
    struct dentry           *debugfs_dentry;
    unsigned            cached_reg_addr;
#endif
};

struct iio_chan_spec

iio_chan_spec is the specification of a single channel:

struct iio_chan_spec {
    enum iio_chan_type  type;
    int         channel;
    int         channel2;
    unsigned long       address;
    int         scan_index;
    struct {
        char    sign;
        u8  realbits;
        u8  storagebits;
        u8  shift;
        enum iio_endian endianness;
    } scan_type;
    long            info_mask;
    long            info_mask_separate;
    long            info_mask_shared_by_type;
    long            event_mask;
    const struct iio_chan_spec_ext_info *ext_info;
    const char      *extend_name;
    const char      *datasheet_name;
    unsigned        modified:1;
    unsigned        indexed:1;
    unsigned        output:1;
    unsigned        differential:1;
};

Configuration Steps

Create DTS Node

The DTS device node of the ADC is already defined in file kernel/arch/arm/boot/dts/rk3288.dtsi:

 adc: adc@ff100000 {
        compatible = "rockchip,saradc";
        reg = <0xff100000 0x100>;
        interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
        #io-channel-cells = <1>;
        io-channel-ranges;
        rockchip,adc-vref = <1800>;
        clock-frequency = <1000000>;
        clocks = <&clk_saradc>, <&clk_gates7 1>;
        clock-names = "saradc", "pclk_saradc";
        status = "disabled";
};

You just need to add the channel node and change its status to "okay" in file kernel/arch/arm/boot/dts/firefly-rk3288.dts, as shown below:

&adc {
    status = "okay";
adc_test{
        compatible = "rockchip,adc_test";
        io-channels = <&adc 0>;
    };
};

Match DTS Node in Driver

Define an array of struct of_device_id:

static const struct of_device_id of_XXX_match[] = { 
    { .compatible = "rockchip,adc_test" },
    { /* Sentinel */ }
};

Note: the compatible attribute must match the devcie node’s compatible, otherwise it will not work.

Fill the .of_match_table member of platform_driver with of_XXX_match:

static struct platform_driver XXX_driver = { 
    .probe      = ...,
    .remove     = ...,
    .driver     = { 
        .name   = "..",
        .owner  = THIS_MODULE,
#ifdef CONFIG_OF
        .of_match_table = of_XXX_match,
#endif
    },
};

Get Device Channel

struct iio_channel *chan;
chan = iio_channel_get(&pdev->dev, NULL);

Note: iio_channel_get gets the channel from the pdev which is paramter of the probe function shown below:

static int XXX_probe(struct platform_device *pdev);

Read Raw Data from Channel

int val,ret;
ret = iio_read_channel_raw(chan, &val);

Call iio_read_channel_raw to read the raw data from the channel and store the result in val parameter.

Convert the Raw Data to Voltage

Use the standard voltage to convert the raw data to the result voltage. The equation is shown as below:

Vref / (2^n-1) = Vresult / raw

Note:

  1. Vref is the standard voltage
  2. n is the AD conversion accuracy, such as 10 bits or 12 bits.
  3. Vresult is the conversion result of the voltage.
  4. raw is the raw data read from function iio_read_channel_raw.

For example, the standard voltage is 1.8V, n is 10 bits, raw data is 568. The conversion expression is:

Vresult = (1800mv * 568) / 1023;

ADC API

/**
 * iio_channel_get() - get description of all that is needed to access channel.
 * @dev:        Pointer to consumer device. Device name must match
 *          the name of the device as provided in the iio_map
 *          with which the desired provider to consumer mapping
 *          was registered.
 * @consumer_channel:   Unique name to identify the channel on the consumer
 *          side. This typically describes the channels use within
 *          the consumer. E.g. 'battery_voltage'
 */
struct iio_channel *iio_channel_get(struct device *dev, const char *consumer_channel);


/**
 * iio_channel_release() - release channels obtained via iio_channel_get
 * @chan:       The channel to be released.
 */
void iio_channel_release(struct iio_channel *chan);


/**
 * iio_read_channel_raw() - read from a given channel
 * @chan:       The channel being queried.
 * @val:        Value read back.
 *
 * Note raw reads from iio channels are in adc counts and hence
 * scale will need to be applied if standard units required.
 */
int iio_read_channel_raw(struct iio_channel *chan, int *val);