![]() |
|
| Home Analog Devices Feedback Subscribe Archives 简体中文 日本語 | |
|
|
|
|
Free and Open-Source SoftwareAn Analog Devices Perspective As a System Designer,
Why Should I care About Free and Open-Source Software? So, for most embedded developers, if FOSS is not in your design today, most likely it will be soon. What Is FOSS?
Being free to do these things means (among other things) that you do not have to ask (or pay) for permission to do so. This is a matter of freedom, not commerce, so think of "free speech, not free beer."3 Also note that the freedoms are for the end user, not the developer, nor the person who distributes the software. On the other hand, open-source software does not always provide the end user the same freedoms, but it does provide developers such rights as access to the source.4 Various open-source licenses allow developers to create proprietary, closed-source software, which includes no requirement to distribute the source code for the end work. The BSD (Berkeley Software Distribution) license is an example of those that allow binary redistributions without source code.5 The key real-world difference between FOSS and closed-source, or proprietary, software is the mass collaborative nature of developmentthe large number of people working independently on individual projects; here any user can become a developerreporting and fixing bugs or adding new features. The popularity of FOSS in the embedded markets is dominated by simple economic motivation6it lowers software costs and hastens time to market. It turns "roll-your-own" developers into system-level integrators who can focus on adding value and differentiating features of their products rather than reproducing the same base infrastructure over and over again. It is the only proven methodology to reel in out-of-control software development costs. Irrespective of the organization, one can almost always find one of the five stages of open-source adoption taking place (our apologies to the late Dr. Kübler-Ross).7 Five Stages of FOSS Adoption8
While many people equate FOSS with the prominent Linux® kernel, or a Linux-based distribution, the use of FOSS beyond Linux in embedded development is pervasive; it is used by almost three-quarters of organizations and spans hundreds of thousands of projects. However, with the increasing popularity of embedded Linux-based systems, the interest in finding Linux drivers for embedded peripherals (ADCs, DACs, audio codecs, accelerometers, touch screen controllers, etc.) becomes increasingly compelling. We will discuss here the contributions of Analog Devices, Inc. (ADI), to various FOSS projects, focusing on the Linux kernel and how they are being used by our customers to reduce risk and decrease product development time. We will take a look at a few popular devices, for example, the ADXL345 digital 3-axis accelerometer, and describe:
Linux Device
DriversArchitecture Independence
This makes clear that architecture-independent drivers (~60% of the kernel source) play a very important role. For each piece of Linux-supported hardware, someone has written a device driver. Since 2007, Analog Devices has ranked within the top 20 companies (from over 300+) contributing code to the Linux kernel10and has a full-time team working on Linux device drivers. Basics of Linux
Device Drivers Operating systems (OS) handle the details of hardware operation for a specific processor platform. Kernel (OS) internal hardware abstraction layers (HALs) and processor-specific peripheral drivers (such as I2C® or SPI bus drivers) allow even a typical device driver to be processor platform independent. This approach allows a device driverfor the AD7879 touch screen digitizer, for exampleto be used without modification on any processor platform running Linux, with any graphical user interface (GUI) package and suitable application running on top of the Linux kernel. If the hardware designer decides to change to the AD7877 touch-screen controller, (s)he can do so without input from their software team. Drivers are available for both devices; and while they differ and can connect differently (the AD7877 is SPI only, and the AD7879 is SPI or I2C)and they both have different register mapsthe kernel API that is exposed to user code for touch screens is exactly the same. This puts control of the hardware back into the hands of the hardware architect. Different types of device drivers in the Linux kernel provide different levels of abstraction. They are generally and historically classified into three categories.11
Each particular category may feature several independent device core layers within the Linux kernel, helping developers to implement drivers that serve standardized purposessuch as video, audio, network, input device, or backlight handling. Typically, each one of these subsystems has its own directory in the Linux kernel source tree. This device driver core approach removes code that would otherwise be common to all device drivers of a specific class and builds a standardized interface to the upper layer. Each class device, or bus device core driver, typically exports a set of functions to its child. Drivers register with such core drivers and use the API exported by the core driver instead of registering a character/block/network driver of their own. This typically includes support and handling for multiple instancesand the way data is distributed between layers. Huge portions of the system have very little interest in how devices are connected, but they need to know what kinds of devices are available. The Linux device model also includes a mechanism to assign devices to a specific class, such as input, RTC (real-time clock), net (networking), or GPIO (general-purpose input/output). These class names describe such devices at a higher, functional level and allow them to be discovered from user space. There may be several device-driver subsystems associated with a particular piece of hardware. A multifunction chip, like the ADP5520 backlight driver with I/O expander, concurrently leverages the Linux backlight, LED, GPIO, and input subsystems for its keypad functionality. As noted earlier, user applications are not allowed to communicate with hardware directly because that would require supervisor privileges on the processor, such as executing special instructions or handling interrupts. Applications utilizing a specific hardware device typically operate on kernel drivers exposed via nodes in the /dev directory. Device nodes are called pseudofiles: they look like files; applications can also open() or close() them; but when they are read or written, the data comes from or is passed to the device nodes' associated driver. This level of abstraction is handled by the virtual file system (VFS) inside the Linux kernel. Besides read(), write(), or poll(), user applications may also interact with a device using ioctl() (input/output control). In addition to the device nodes, applications may also utilize file entries in /sys, a sysfs virtual file system that exports information about devices and drivers, including parent/child relationship or association to a specific class or bus, from the kernel device model to user space. /sys is also heavily used for device configuration, especially when the driver in question registers with a device driver core, which exports only its standardized set of functionality to the user.
Device drivers can register /sys hooks or entries; a specially registered callback function from the device driver gets executed when they are read or written. These callback functionsrunning in supervisor modemay accept parameters, initiate bus transfers, invoke some processing, modify device-specific variables, and return integer values or character strings back to the user. This allows additional functionality; for example, the temperature sensor or auxiliary ADCs on the AD7877 touch-screen digitizer can be made available to user space. Device drivers can be statically built into the kernel, or dynamically installed later as loadable modules. Linux kernel modules (LKMs) are dynamic components that can be inserted and removed at run time. This is especially valuable to driver developers since time is saved by quicker compilation and by not having to reboot the system to test the module. By letting the hardware drivers reside in modules that can be loaded into the kernel at any time, it is possible to save RAM when the specific hardware is not in use. When a module is loaded, it can also be given configuration parameters. For a module that is built into the kernel, parameters are passed to it during the kernel boot. For example: root:~> insmod ./sample_module.ko argument=1 root:~> lsmod Module Size Used by sample_module 1396 0 - Live 0x00653000 root:~> rmmod sample_module Drivers can also be instantiated multiple times, with different settings, with the target device sitting on a different I2C slave ID, connected to a different SPI slave select, or mapped to a different physical memory address. All instances share the same code, which saves memory, but will have individual data sections. Since Linux is a preemptive multitasking, multiuser operating system, almost all device drivers and kernel subsystems are designed to allow multiple processes (possibly owned by different users) to leverage the devices concurrently. Popular examples are the network, audio, or input interfaces. Key-press or -release events of an ADP5588 QWERTY keypad controller are time-stamped, queued, and sent to all processes that opened the input event device. These event codes are the same on all architectures and are hardware independent. There is no difference between reading a USB keyboard and reading the ADP5588 from user space. Event types are differentiated from codes. A keypad sends key-events (EV_KEY), together with codes identifying the key and some state value representing the press- or release action. A touch screen sends absolute coordinate events (EV_ABS) with a triplet consisting of x, y, and touch pressure, while a mouse sends relative movement events (EV_REL). An ADXL346 accelerometer may send key events for tap or double taps while it sends absolute-coordinate events for the acceleration. In some applications, it could also make sense if the ADXL346 accelerometer generated relative events, or sent a specific key codevery application-specific settings. In general, there are two ways of driver customization: during run time or during compile time. Device characteristics that are likely to be customized during run time use module parameters or /sys entries. Using an Open-Source
Linux DriverCustomization for a Specific Target For devices on custom boards, as typical of embedded and SoC-(system-on-a-chip) based hardware, Linux uses platform_data to point to board-specific structures describing devices and how they are connected to the SoC. This can include available ports, chip variants, preferred modes, default initialization, additional pin roles, and so on. This shrinks the board-support packages (BSPs) and minimizes board and application specific #ifdefs in drivers. It is up to the driver's author to decide which tunables go into platform_data and which should have access during run time. Digital accelerometer characteristics are highly application-specific and may differ between boards and models. The following example shows a set of these configuration options. These variables are fully documented in the header file, adxl34x.h (include/linux/input/adxl34x.h). #include <linux/input/adxl34x.h> static const struct adxl34x_platform_data adxl34x_info = { .x_axis_offset = 0, .y_axis_offset = 0, .z_axis_offset = 0, .tap_threshold = 0x31, .tap_duration = 0x10, .tap_latency = 0x60, .tap_window = 0xF0, .tap_axis_control = ADXL_TAP_X_EN | ADXL_TAP_Y_EN | ADXL_TAP_Z_EN, .act_axis_control = 0xFF, .activity_threshold = 5, .inactivity_threshold = 3, .inactivity_time = 4, .free_fall_threshold = 0x7, .free_fall_time = 0x20, .data_rate = 0x8, .data_range = ADXL_FULL_RES, .ev_type = EV_ABS, .ev_code_x = ABS_X, /* EV_REL */ .ev_code_y = ABS_Y, /* EV_REL */ .ev_code_z = ABS_Z, /* EV_REL */ .ev_code_tap = {BTN_TOUCH, BTN_TOUCH, BTN_TOUCH}, /* EV_KEY x,y,z */ .ev_code_ff = KEY_F, /* EV_KEY */ .ev_code_act_inactivity = KEY_A, /* EV_KEY */ .power_mode = ADXL_AUTO_SLEEP | ADXL_LINK, .fifo_mode = ADXL_FIFO_STREAM, }; To attach devices to drivers, the platform and bus model eliminates the need for device drivers to contain hard-coded physical addresses or bus IDs of the devices they control. The platform and bus model also prevents resource conflicts, greatly improves portability, and cleanly interfaces with the kernel's power-management features. With the platform and bus model, device drivers know how to control a device once informed of its physical location and interrupt lines. This information is provided as a data structure passed to the driver during probing. Unlike PCI or USB devices, I2C or SPI devices are not enumerated at the hardware level. Instead, the software must know which devices are connected on each I2C/SPI bus segment and what address (slave selects) these devices are using. For this reason, the kernel code must instantiate I2C/SPI devices explicitly. There are different ways to achieve this, depending on the context and requirements. However, the most common method is to declare the I2C/SPI devices by bus number. This method is appropriate when the I2C/SPI bus is a system bus, as in many embedded systems, wherein each I2C/SPI bus has a number which is known in advance. It is thus possible to pre-declare the I2C/SPI devices that inhabit this bus. This is done with an array of struct i2c_board_info / spi_board_info, which is registered by calling i2c_register_board_info()/spi_register_board_info() static struct i2c_board_info __initdata bfin_i2c_board_info[] = { #if defined(CONFIG_TOUCHSCREEN_AD7879_I2C) || defined(CONFIG_TOUCHSCREEN_AD7879_I2C_MODULE) { I2C_BOARD_INFO("ad7879", 0x2F), .irq = IRQ_PG5, .platform_data = (void *)&bfin_ad7879_ts_info, }, #endif #if defined(CONFIG_KEYBOARD_ADP5588) || defined(CONFIG_KEYBOARD_ADP5588_MODULE) { I2C_BOARD_INFO("adp5588-keys", 0x34), .irq = IRQ_PG0, .platform_data = (void *)&adp5588_kpad_data, }, #endif #if defined(CONFIG_PMIC_ADP5520) || defined(CONFIG_PMIC_ADP5520_MODULE) { I2C_BOARD_INFO("pmic-adp5520", 0x32), .irq = IRQ_PG0, .platform_data = (void *)&adp5520_pdev_data, }, #endif #if defined(CONFIG_INPUT_ADXL34X_I2C) || defined(CONFIG_INPUT_ADXL34X_I2C_MODULE) { I2C_BOARD_INFO("adxl34x", 0x53), .irq = IRQ_PG0, .platform_data = (void *)&adxl34x_info, }, #endif }; static void __init blackfin_init(void) { (...) i2c_register_board_info(0, bfin_i2c_board_info, ARRAY_SIZE(bfin_i2c_board_info)); spi_register_board_info(bfin_spi_board_info, ARRAY_SIZE(bfin_spi_board_info)); (...) } So, to enable such a driver one need only edit the board support file by adding an appropriate entry to i2c_board_info (spi_board_info). It has also been noted that the driver needs to be selected during kernel configuration. Drivers are sorted by subsystems they belong to. The ADXL34x driver can be found under: Device Drivers ---> Input device support ---> [*] Miscellaneous devices ---> <M> Analog Devices AD714x Capacitance Touch Sensor <M> support I2C bus connection <M> support SPI bus connection <*> Analog Devices ADXL34x Three-Axis Digital Accelerometer <*> support I2C bus connection <*> support SPI bus connection Selected drivers will be compiled automatically once the user has started the kernel build process. The above code declares
four devices on I2C Bus 0, including their respective addresses,
IRQ, and custom platform_data needed by their drivers. When the I2C
bus in question is registered, the I2C devices will be instantiated
automatically by the static struct i2c_driver adxl34x_driver = { .driver = { .name = "adxl34x", .owner = THIS_MODULE, }, .probe = adxl34x_i2c_probe, .remove = __devexit_p(adxl34x_i2c_remove), .suspend = adxl34x_suspend, .resume = adxl34x_resume, .id_table = adxl34x_id, }; static int __init adxl34x_i2c_init(void) { return i2c_add_driver(&adxl34x_driver); } module_init(adxl34x_i2c_init); At some point during kernel startup, or at any time later, a device driver named "adxl34x" may register itself, using struct i2c_driverwhich is registered by calling i2c_add_driver(). Members of struct i2c_driver are set with pointers to ADXL34x driver functions, connecting the driver with its bus master core. (The module_init() macro defines which function (adxl34x_i2c_init()) is to be called at module insertion time.) If the name of the driver that is filed matches the name given with the I2C_BOARD_INFO macro, the i2c-core bus model implementation invokes the driver's probe() function, passing it the associated platform_data and irq from the board support file to the driver. This only happens in cases without recourse conflicts, such as when a previously instantiated device uses the same I2C slave address. The adxl34x_i2c_probe() function then starts to do what its name implies. It checks if either an ADXL345 or ADXL346 device is present and functional by reading the manufacturer and device ID. If this succeeds, the driver's probe function allocates device-specific data structures, requests the interrupt, and initializes the accelerometer. It then allocates a new input device structure using input_allocate_device() and sets up input bit fields. In this way, the device driver tells the other parts of the input systems what it is and what events can be generated by this new input device. Finally the ADXL34x driver registers the input device structure by calling input_register_device(). This adds the new input device structure to linked lists of the input driverand calls device-handler modules' connect functions to tell them a new input device has appeared. From this point on, the device may generate interrupts. The interrupt service routine, once executed, reads the status registers and event FIFOs from the accelerometer and sends appropriate events back to the input subsystem using input_event(). Drivers Maintained
by Analog Devices To get help with these drivers, in the standard open-source fashion, Analog Devices sponsors a website that includes web forums and mailing lists at http://blackfin.uclinux.org/gf/where a full-time ADI team is responsible for answering questions and handling requests about FOSS drivers in a timely manner. References
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||