Gate Clock Driver Case Study-Free Linux Device Drivers Course

Gate Clock Driver Case Study-Free Linux Device Drivers Course
Free Linux Kernel Development Course · Common Clock Framework · Kernel 6.x
Chapter 4 · Lecture 19
Reading Time: 15 min
Level: Intermediate

Keywords covered in this lecture

linux gate clock driver
struct clk_gate
clk_hw_register_gate
CLK_GATE_HIWORD_MASK
free linux kernel development course
free linux device drivers course

Of the base clock types this chapter has covered, the linux gate clock
driver
is the one you will write most often in real MMIO-based clock
controllers — it does exactly one job: turn a clock signal on or off through a
single register bit. This lecture works through struct clk_gate field by field, the
hiword-mask register trick some SoCs require, and the registration helper that wraps
it all up.

What You Will Learn

  • The struct clk_gate wrapper and its reg/bit_idx/lock fields
  • CLK_GATE_SET_TO_DISABLE and what it means to invert gate polarity
  • CLK_GATE_HIWORD_MASK and the hiword-mask register write pattern
  • clk_hw_register_gate() and the built-in clk_gate_ops it wires up
  • Registering an original MMIO gate clock end to end

Prerequisites

  • Fixed-factor clock driver case study (previous lecture)
  • Mandatory clk_ops by clock type (Gate column)
  • clk_hw embedding and container_of pattern

The struct clk_gate Wrapper

A gate clock’s entire hardware-specific state is: which register to write, which bit
in it controls the gate, and what locking is needed to write it safely.

struct clk_gate {
    struct clk_hw hw;
    void __iomem *reg;
    u8 bit_idx;
    u8 flags;
    spinlock_t *lock;
};

#define to_clk_gate(_hw) container_of(_hw, struct clk_gate, hw)
  • reg: the MMIO virtual address of the register controlling this gate
  • bit_idx: which bit in that register turns the clock on or off
  • flags: gate-specific behavior flags, covered next
  • lock: a spinlock shared with any sibling clocks that share the same register

This structure assumes the gate register is plain MMIO — a detail the next
lecture builds directly on when covering gate clocks that live behind I2C or SPI
instead.

Gate-Specific Flags

Two flags control how the register write itself behaves:

  • CLK_GATE_SET_TO_DISABLE: inverts the usual polarity. With this flag set, writing 1 turns the clock off and writing 0 turns it on, instead of the default the other way around
  • CLK_GATE_HIWORD_MASK: uses a hiword-mask register instead of a plain read-modify-write

Understanding Hiword Mask

Some hardware registers cannot be safely read-modify-written — writing one bit
might unintentionally reset others due to how the hardware latches the value. Hiword
mask solves this within a single 32-bit register: the lower 16 bits (0-15) hold the
actual gate bit values, while the upper 16 bits (16-31) act as a per-bit write-enable
mask. To change bit b1, you set bit b1 in the lower half to
the new value and set bit b1 << 16 in the upper half to
signal that this particular bit should actually be applied. Every other bit position is
left alone automatically. Because only 16 bits are available for the actual gate
values, bit_idx must be 15 or lower whenever this flag is used.

Registering With clk_hw_register_gate()

struct clk_hw *clk_hw_register_gate(struct device *dev, const char *name,
                                     const char *parent_name,
                                     unsigned long flags,
                                     void __iomem *reg, u8 bit_idx,
                                     u8 clk_gate_flags, spinlock_t *lock);

flags here are the framework-level flags from an earlier lecture —
it is common to set CLK_SET_RATE_PARENT on a gate clock that has a parent,
so rate-change requests are forwarded up one level, since a plain gate has no rate logic
of its own. clk_gate_flags corresponds to the wrapper struct’s own
flags field (CLK_GATE_SET_TO_DISABLE,
CLK_GATE_HIWORD_MASK).

The framework assigns its own built-in ops automatically before registration:

const struct clk_ops clk_gate_ops = {
    .enable     = clk_gate_enable,
    .disable    = clk_gate_disable,
    .is_enabled = clk_gate_is_enabled,
};
EXPORT_SYMBOL_GPL(clk_gate_ops);

Exactly the three callbacks the Gate column of the mandatory-callbacks table two
lectures ago said were required — nothing more, since a gate clock cannot change
rate or select between parents.

Original Demo: An MMIO Gate Clock

/* ep_mmio_gate_demo.c - a gate clock controlled through a single register bit */
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/spinlock.h>

struct ep_gate_demo_data {
    void __iomem *base;
    spinlock_t lock;
    struct clk_hw *gate_hw;
};

static int ep_mmio_gate_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct ep_gate_demo_data *data;

    data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    spin_lock_init(&data->lock);

    data->base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(data->base))
        return PTR_ERR(data->base);

    /* bit 3 of the gate register controls this peripheral clock */
    data->gate_hw = devm_clk_hw_register_gate(dev, "ep_periph_gate", "ep_bus_clk",
                                               CLK_SET_RATE_PARENT,
                                               data->base, 3, 0, &data->lock);
    if (IS_ERR(data->gate_hw))
        return PTR_ERR(data->gate_hw);

    return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, data->gate_hw);
}

static const struct of_device_id ep_mmio_gate_of_match[] = {
    { .compatible = "ep,clk-gate-demo" },
    { }
};
MODULE_DEVICE_TABLE(of, ep_mmio_gate_of_match);

static struct platform_driver ep_mmio_gate_driver = {
    .probe = ep_mmio_gate_probe,
    .driver = {
        .name = "ep_clk_gate_demo",
        .of_match_table = ep_mmio_gate_of_match,
    },
};
module_platform_driver(ep_mmio_gate_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala MMIO gate clock demo");

Matching device tree node, deriving from the critical bus clock introduced in an
earlier lecture:

ep_gate_ctrl: clock-controller@7000 {
    compatible = "ep,clk-gate-demo";
    reg = <0x7000 0x4>;
    #clock-cells = <0>;
    clocks = <&ep_bus_clk>;
};

Build and Run

make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
sudo insmod ep_mmio_gate_demo.ko
cat /sys/kernel/debug/clk/clk_summary | grep ep_periph_gate

Expected clk_summary excerpt, showing the gate initially enabled (a driver would call
clk_prepare_enable()/clk_disable_unprepare() to control it at
runtime):

ep_periph_gate            1        1        0   100000000          0

Registering the Same Gate With Hiword Mask

If the same bit lived in a register that only supports hiword-mask writes, only the
clk_gate_flags argument changes — nothing else about the driver:

data->gate_hw = devm_clk_hw_register_gate(dev, "ep_periph_gate", "ep_bus_clk",
                                           CLK_SET_RATE_PARENT,
                                           data->base, 3,
                                           CLK_GATE_HIWORD_MASK, &data->lock);

Internally, clk_gate_enable()/clk_gate_disable() then write
both the target bit and its corresponding mask bit in the upper 16 bits, instead of doing
a plain read-modify-write on the register.

Plain vs Hiword-Mask Gate Registers

Aspect Plain Gate Register Hiword-Mask Gate Register
Write style Read-modify-write Direct write with an inline mask
Max bit_idx Up to register width 15
Flag needed None CLK_GATE_HIWORD_MASK

Common Mistakes

  • Using CLK_GATE_HIWORD_MASK with a bit_idx above 15
  • Forgetting CLK_GATE_SET_TO_DISABLE on hardware where writing 1 actually disables the clock
  • Sharing a register across multiple gate clocks without a common spinlock
  • Assuming struct clk_gate works for a gate register behind I2C or SPI

Best Practices

  • Use clk_hw_register_gate()/devm_ variant instead of building struct clk_gate by hand
  • Share one spinlock across every gate clock backed by the same register
  • Check the datasheet for hiword-mask support before assuming plain read-modify-write is safe
  • Set CLK_SET_RATE_PARENT whenever the gate sits beneath a rate-changing parent

Summary and Key Takeaways

  • struct clk_gate holds reg, bit_idx, flags, and lock alongside the embedded clk_hw
  • CLK_GATE_SET_TO_DISABLE inverts polarity; CLK_GATE_HIWORD_MASK changes the write style entirely
  • clk_hw_register_gate() wires up clk_gate_ops (enable/disable/is_enabled) automatically
  • This structure assumes an MMIO register, which the next lecture explicitly does not have

Frequently Asked Questions

What does CLK_GATE_SET_TO_DISABLE actually change?

It inverts the gate’s write polarity: writing 1 disables the clock and writing 0
enables it, the opposite of the default behavior.

Why is bit_idx limited to 15 under hiword mask?

Because only the lower 16 bits of the 32-bit register carry actual gate values; the
upper 16 bits are reserved entirely for the write-enable mask.

Can multiple gate clocks share the same spinlock?

Yes, and they should whenever they share the same underlying register, to avoid a
race between two gate bits being modified at once.

Does a gate clock need CLK_SET_RATE_PARENT?

Only if it sits beneath a parent that can change rate and you want rate-change
requests on the gate to propagate upward; a gate has no rate logic of its own either
way.

Where is the built-in gate clock implementation defined?

drivers/clk/clk-gate.c in the kernel source tree, which implements clk_gate_ops and
the registration helper covered in this lecture.

Suggested Images For This Lecture

  • Diagram: struct clk_gate fields (reg, bit_idx, flags, lock)
  • Diagram: hiword mask register layout, lower 16 bits vs upper 16 bits
  • Screenshot: clk_summary showing a registered MMIO gate clock

Continue This Free Linux Kernel Development Course

Next, we look at gate clocks that live behind I2C or SPI instead of MMIO, and the
DT-only gpio-gate-clock alternative.

Leave a Reply

Your email address will not be published. Required fields are marked *