CDS9K Linux Kernel Drivers
This repo houses Linux kernel drivers for the CDS9K FPGA/CPLD.
This is a semi-fictional piece of hardware I'm developing for classroom/demo sessions on various aspects of Linux kernel driver development - it's not on any real boards, it's just a teaching tool. I do have a VHDL implementation which I will include once the IP blocks are more stable.
Unless otherwise noted, for all reserved bits in registers, read as "don't care" and write as 0.
IP Block: cds9k-fan
The CDS9K fan control block allows PWM speed control of a single fan, and readback of the fan's tachometer.
Register 0x0: FAN_PWM
Mode: read-write
Write this register to set the fan PWM duty.
Bits | 15 - 8 | 7 - 0 |
---|---|---|
Use | reserved | duty_val |
The value for duty_val
can be determined as:
duty_val = duty_percent * 2.55
i.e. a linear scaling from 0-100% to 0x0-0xFF
Register 0x1: FAN_TACH
Mode: read-only
Read this register to determine the fan tachometer reading in revolutions per minute (RPM).
Bits | 15 - 0 |
---|---|
Use | fan_rpm |
IP Block: cds9k-led
The CDS9K LED control block allows PWM brightness control of a single LED (or LED die, in the case of multi-colour LEDs) as well as hardware-accelerated blinking with 50% duty and variable period.
Two signals are generated internally: blink and PWM. These are ANDed together to form the LED output. A sample waveform diagram (relative timings not to scale):
blink ---------------- ---------------- -
---------------- ----------------
PWM - - - - - - - - - - - - -
---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
led - - - - - - -
drive ---- ---- ---- ------------------- ---- ---- -------------------
The blink
signal has a fixed duty of 50% and a period programmable between
20 milliseconds and 5.1 seconds. The PWM signal has a fixed period of 40
microseconds (i.e. 25 kHz), and a duty programmable between 0 and 100%.
Register 0x0: LED_PWM_DUTY
Mode: read-write
Write this register to set the LED PWM duty. The PWM duty can be calculated as:
Bits | 15 - 8 | 7 - 0 |
---|---|---|
Use | reserved | duty_val |
The value for duty_val
can be determined as:
duty_val = duty_percent * 2.55
i.e. a linear scaling from 0-100% to 0x0-0xFF
Register 0x0: LED_BLINK_PERIOD
Mode: read-write
Write this register to set the LED blink period. When a non-zero value is written to this register, the LED output is gated by a 50% duty square wave with period:
period_val = 20 milliseconds * LED_BLINK_PERIOD
In this way, the LED can be blinked on and off with 50% duty at a programmable
frequency. When the LED should be enabled during each cycle, it is driven at
the PWM brightness programmed in the LED_PWM_DUTY
register.
Bits | 15 - 8 | 7 - 0 |
---|---|---|
Use | reserved | period_val |
IP Block: cds9k-gpio
The CDS9K GPIO block provides access to 16 basic I/O lines. Interrupts and pull-ups/pull-downs are not available. GPIO lines are individually selectable between input/hi-z and output.
Register: 0x0: PORT
Mode: read-write
Bits | 15 - 0 |
---|---|
Use | port |
Register: 0x1: DIRECTION
Mode: read-write
Bits | 15 - 0 |
---|---|
Use | direction_mask |
IP Block: cds9k-reset
The CDS9K reset control block exposes a single reset line which can be asserted and deasserted by writing different magic values to a single 16-bit register.
Register: 0x0: REG0
Mode: read-write
Write as 0xDEAD to assert the reset, and write as 0x0000 to deassert the reset.
Bits | 15 - 0 |
---|---|
Use | reset_magic |
Register: 0x1: RESERVED
Mode: read-only
Write as 0xDEAD to assert the reset, and write as 0x0000 to deassert the reset.
Bits | 15 - 0 |
---|---|
Use | reserved |
Misc drivers
There is currently one misc driver in this repo too: simple-reset-consumer
.
On probe, this driver takes a single reset from the device-tree and asks it to
reset. This is the quickest way I had for hackily debugging the effect of a
reset driver such as cds9k-reset, as there doesn't appear to be any sysfs or
debugfs nodes exposed for reset controllers in general.
If you include a segment in your device tree like:
some_reset_consumer {
compatible = "david,simple-reset-consumer";
resets = <&some_reset>;
status = "okay";
};
Then firing the resets looks like:
root@de10-nano:~# rmmod simple-reset-consumer
root@de10-nano:~# modprobe simple-reset-consumer
[ 995.241046] simple-reset-consumer some_reset_consumer: firing reset
root@de10-nano:~#