Linux Kernel Driver
Linux Kernel Driver
driver programming
Introduction to Linux kernel driver
programming
A single driver for compatible devices, though connected to buses with different controllers.
Device drivers (1)
Need to register supported devices to the bus core.
Example: drivers/net/usb/rtl8150.c
static struct usb_device_id rtl8150_table[] =
{{ USB_DEVICE(VENDOR_ID_REALTEK, PRODUCT_ID_RTL8150) },
{ USB_DEVICE(VENDOR_ID_MELCO, PRODUCT_ID_LUAKTX) },
{ USB_DEVICE(VENDOR_ID_MICRONET, PRODUCT_ID_SP128AR) },
{ USB_DEVICE(VENDOR_ID_LONGSHINE, PRODUCT_ID_LCS8138TX) },[…]
{}
};
MODULE_DEVICE_TABLE(usb, rtl8150_table);
Device drivers (2)
Need to register hooks to manage devices (newly detected or removed ones), as
well as to react to power management events (suspend and resume)
Now the bus driver knows the association between the devices and the device driver.
Work in the probe() function
probe() is called for each newly matched device
●
Initialize the device
●
Prepare driver work: allocate a structure for a suitable
framework, allocate memory, map I/O memory, register
interrupts…
●
When everything is ready, register the new device to
the framework.
At driver loading time
●
The USB adapter driver that
corresponds to the USB controller
registers itself to the USB core
● The rtl8150 USB device driver
registers itself to the USB core
●
The USB core now knows the
association between the vendor/product
IDs of rtl8150 and the struct
usb_driver structure of this driver
When a device is detected
The model is recursive
...
i2c0: i2c@44e0b000 {
compatible = "ti,omap4-i2c"; Compatible drivers
#address-cells = <1>;
#size-cells = <0>;
Node ti,hwmods = "i2c1";
properties reg = <0x44e0b000 0x1000>; HW register start
interrupts = <70>; address and range
status = "disabled"; Present but not
}; used by default
...
From arch/arm/boot/dts/am33xx.dtsi
Instantiating a device: .dts example
&i2c0 { Pin muxing configuration
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins>;
(routing to external package pins)
Phandle
status = "okay"; Enabling this device, otherwise ignored
(reference clock-frequency = <400000>; Node property: frequency
to label)
tps: tps@24 {
reg = <0x24>;
};
baseboard_eeprom: baseboard_eeprom@50 {
List of devices on
compatible = "at,24c256"; i2c0
reg = <0x50>; I2C bus identifier
#address-cells = <1>;
#size-cells = <1>;
baseboard_data: baseboard_data@0 {
reg = <0 0x100>;
};
};
}; From arch/arm/boot/dts/am335x-boneblue.dts
Pin multiplexing
●
Modern SoCs have too many
hardware blocks compared to physical
pins exposed on the chip package.
●
Therefore, pins have to be multiplexed
●
Pin configurations are defined in the
Device Tree
●
Correct pin multiplexing is mandatory
to make a device work from an
electronic point of view.
DT pin definitions
&am33xx_pinmux {
...
i2c0_pins: pinmux_i2c0_pins {
pinctrl-single,pins = <
AM33XX_IOPAD(0x988, PIN_INPUT_PULLUP | MUX_MODE0) /* (C17) I2C0_SDA.I2C0_SDA */
AM33XX_IOPAD(0x98c, PIN_INPUT_PULLUP | MUX_MODE0) /* (C16) I2C0_SCL.I2C0_SCL */
>;
};
…
}; Register offset corresponding Allows to select Package pin SoC signal
to a given package pin a given SoC signal name name
... Configures the pin:
input, output, drive
&i2c0 { strengh, pull up/down...
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins>;
status = "okay";
clock-frequency = <400000>;
... From arch/arm/boot/dts/am335x-boneblue.dts
};
DT: matching devices and drivers
Platform drivers are matched with platform devices that have the same compatible
property. static const struct of_device_id omap_i2c_of_match[] = {
{
.compatible = "ti,omap4-i2c",
.data = &omap4_pdata,
},
{
…
};
…
static struct platform_driver omap_i2c_driver = {
.probe = omap_i2c_probe,
.remove = omap_i2c_remove,
.driver = {
.name = "omap_i2c",
.pm = OMAP_I2C_PM_OPS,
.of_match_table = of_match_ptr(omap_i2c_of_match),
},
};
From drivers/i2c/busses/i2c-omap.c
Usage of the platform bus
Like for physical buses, the platform bus is used by the driver to retrieve information
about each device
static int omap_i2c_probe(struct platform_device *pdev)
{
...
struct device_node *node = pdev→dev.of_node;
struct omap_i2c_dev *omap;
...
irq = platform_get_irq(pdev, 0);
…
omap = devm_kzalloc(&pdev->dev, sizeof(struct omap_i2c_dev), GFP_KERNEL);
...
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
omap->base = devm_ioremap_resource(&pdev->dev, mem);
u32 freq = 100000; /* default to 100000 Hz */
…
of_property_read_u32(node, "clock-frequency", &freq);
…
return 0;
}
From drivers/i2c/busses/i2c-omap.c
Device tree bindings
●
Device tree bindings provide a specification of properties that
a driver expects in a DT
●
Bindings are available in
Documentation/devicetree/bindings in kernel
sources.
●
To know how to set device properties, look for a binding for
the same compatible string:
$ git grep “ti,omap4-i2c” Documentation/devicetree/bindings/
Another bus example: I2C
I2C drivers: probe() function
static int mma7660_probe(struct i2c_client *client,
const struct i2c_device_id *id) device structure for the i2c bus
{
int ret; needed to communicate with the device
struct iio_dev *indio_dev;
struct mma7660_data *data; Framework (here iio) structure for each device
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev) { Per device structure. Used by the driver
dev_err(&client->dev, "iio allocation failed!\n");
return -ENOMEM; to store references to bus and framework structures,
} plus its own data (locks, wait queues, etc.)
data = iio_priv(indio_dev); Allocation of the framework structure. This structure
data->client = client;
i2c_set_clientdata(client, indio_dev);
also contains the per device structure (data)
mutex_init(&data->lock);
data->mode = MMA7660_MODE_STANDBY; Reference to the bus structure stored in the
framework structure.
indio_dev->dev.parent = &client->dev;
indio_dev->info = &mma7660_info;
indio_dev->name = MMA7660_DRIVER_NAME; Reference to the framework structure stored in the
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = mma7660_channels;
bus structure.
indio_dev->num_channels = ARRAY_SIZE(mma7660_channels);
Enabling the device (i2c reading and writing)
ret = mma7660_set_mode(data, MMA7660_MODE_ACTIVE);
if (ret < 0) Register a new framework device when everything is
return ret;
ready (device now accessible in user-space)
ret = iio_device_register(indio_dev);
if (ret < 0) {
dev_err(&client->dev, "device_register failed\n"); Disabling the device in case of failure
mma7660_set_mode(data, MMA7660_MODE_STANDBY);
}
}
return ret; From drivers/iio/accel/mma7660.c
I2C drivers: remove() function
sstatic int mma7660_remove(struct i2c_client *client) Same i2c device structure as in probe()
{
struct iio_dev *indio_dev = i2c_get_clientdata(client);
Get back the framework structure.
iio_device_unregister(indio_dev); Needed to unregister the framework
device from the system
return mma7660_set_mode(iio_priv(indio_dev),
MMA7660_MODE_STANDBY);
Unregister the framework
}
device from the system
From drivers/iio/accel/mma7660.c
I2C driver registration
static const struct i2c_device_id mma7660_i2c_id[] = {
{"mma7660", 0},
Matching by name (mandatory for I2C)
{}
};
MODULE_DEVICE_TABLE(i2c, mma7660_i2c_id);
static const struct of_device_id mma7660_of_match[] = { Matching by compatible property (for DT)
{ .compatible = "fsl,mma7660" },
{ }
};
MODULE_DEVICE_TABLE(of, mma7660_of_match);
static const struct acpi_device_id mma7660_acpi_id[] = { Matching by ACPI ID (for ACPI systems - x86)
{"MMA7660", 0},
{}
};
MODULE_DEVICE_TABLE(acpi, mma7660_acpi_id);
module_i2c_driver(mma7660_driver);
From drivers/iio/accel/mma7660.c
Driver development advise
●
Look for code for devices similar to yours
●
Read the code.
You can use Elixir (https://elixir.bootlin.com/)
●
Always read code from the bottom up. You see
the big picture first, and then progressively how
the details are implemented.
Further reading
●
Bootlin’s kernel and driver
development training materials for full
details
https://bootlin.com/training/kernel/
●
Device Tree for Dummies presentation
Thomas Petazzoni (Apr. 2014)
http://j.mp/1jQU6NR
Questions?
Thank you!