Received: by 2002:a05:6602:18e:0:0:0:0 with SMTP id m14csp1682628ioo; Mon, 23 May 2022 00:08:51 -0700 (PDT) X-Google-Smtp-Source: ABdhPJzEN5w7WHdcAMjME2RCTsa3OPfReMdTibQUsiex47AY3lyDgxAyY9S0QslhyptPvkCvO23N X-Received: by 2002:a05:6a00:1d8e:b0:518:87e7:db00 with SMTP id z14-20020a056a001d8e00b0051887e7db00mr9300355pfw.84.1653289731179; Mon, 23 May 2022 00:08:51 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1653289731; cv=none; d=google.com; s=arc-20160816; b=sHfcWpyXZ4Oa2WWNPyybtXhgnkVDbEjbnw+US8DNS3a0TqbyNEydJmWaoBeNWvRWpY k5Ug6ngNQdkJJs5oMutMj4AxltOe8oFxSgkbPU3fICvZP1uBZawreVfsG6MY8WkPpMGQ C5XcxHVMxzOjdiI8tObGHgT8LSMMbR97X4X4PpyzmBoHn6j0sOtglNYnJCMdOYJ4bEUI QZ4v96PpJegSsSZRlWjc2SBeHXh9Da2JATtRRXfl6x+HAE7vMykuakwaPaVCrRqcZs+w kMTITm3qR4//ruccIAdfQIc1hce3C52GbnR/F7vCAo/6ii/5M3vsybgWLVUy7b6vv3e5 OE9A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=svPxRvlT3i/gdoRVtYs4rXtXBkTT7GgRGExZnnJ5WiE=; b=sJ5yh46iQJc6J7/ZL0lc/NS5LfHBiMskLqLOpjAqscFCcQnk1XFgorwOF7bCdHEcMp yx6NqK5rnw/rrD8qE7gCYjxIqQlyK7A3yVOQOATIULCgNH9/Kd7IMmlXG94XYE9f7ZmE W82aGV4oiy2FJerZRMDdge4OKx56zAs5qiiQCugvRrmHLuUazAYxxNzNiZ2a+izZfw0+ Jcb/EwyNwTrbH8VOhv8MhtjWAuEeXGdqAUIBNliSnhR7fS/tsA1Uf12RI/Fl7o0jq0oQ QG0foqA1jv6sgYZkBPcmvgKw2+OD3fSJArecemb6318EAq9+r062XhOwY6CFhaX6yko4 o5QQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@kernel.org header.s=k20201202 header.b=sJRHN4Gn; spf=softfail (google.com: domain of transitioning linux-kernel-owner@vger.kernel.org does not designate 23.128.96.19 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=kernel.org Return-Path: Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net. [23.128.96.19]) by mx.google.com with ESMTPS id d6-20020a63d706000000b003c19e2b283esi8950304pgg.494.2022.05.23.00.08.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 23 May 2022 00:08:51 -0700 (PDT) Received-SPF: softfail (google.com: domain of transitioning linux-kernel-owner@vger.kernel.org does not designate 23.128.96.19 as permitted sender) client-ip=23.128.96.19; Authentication-Results: mx.google.com; dkim=pass header.i=@kernel.org header.s=k20201202 header.b=sJRHN4Gn; spf=softfail (google.com: domain of transitioning linux-kernel-owner@vger.kernel.org does not designate 23.128.96.19 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=kernel.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 906756D3A1; Sun, 22 May 2022 23:28:54 -0700 (PDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1352499AbiEWCGm (ORCPT + 99 others); Sun, 22 May 2022 22:06:42 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:40876 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1352747AbiEWCFT (ORCPT ); Sun, 22 May 2022 22:05:19 -0400 Received: from ams.source.kernel.org (ams.source.kernel.org [IPv6:2604:1380:4601:e00::1]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 07AB93AA52; Sun, 22 May 2022 19:04:57 -0700 (PDT) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ams.source.kernel.org (Postfix) with ESMTPS id 98645B80E91; Mon, 23 May 2022 02:04:55 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 03F92C34113; Mon, 23 May 2022 02:04:51 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1653271494; bh=Dl7vb3MilvTiwLd3lJm+1EX8DNBV+Tisqtx2al63TnI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=sJRHN4GnFpmzbQqrmnxRoDl9+Qb2krpO131H3pkbHTpfVXP7VYGokTqu9BYsntRdn zQmJMk6gQRPTcQ4cnEwBjqecrSx0Wku2FrtQS92Y35G5O04uBB5oBTO3D7o7Pc2DJF r6S+eXREGClesmwZNzV0FnbY88zuVWZlGnzSS+JG3DO6309pDiHJa0lOVH4eLifEpP lvYnzoQBn0l7Kdbux14mAAC7GVz0b4j1fEsP9896wXibhVF0kqTgOTHI8urVb07xTp Tf5ATs3mXXdlWW0CgDtUyC+cWE4ElhMHA8xExDDdqUXwMS6p9j7+OzEfoGkjIc4OLR a9K9xe+kqL19w== From: Miguel Ojeda To: Linus Torvalds , Greg Kroah-Hartman Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, Jarkko Sakkinen , Miguel Ojeda , Wedson Almeida Filho , Linus Walleij , Bartosz Golaszewski , linux-gpio@vger.kernel.org Subject: [PATCH v7 24/25] [RFC] drivers: gpio: PrimeCell PL061 in Rust Date: Mon, 23 May 2022 04:01:37 +0200 Message-Id: <20220523020209.11810-25-ojeda@kernel.org> In-Reply-To: <20220523020209.11810-1-ojeda@kernel.org> References: <20220523020209.11810-1-ojeda@kernel.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-2.9 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,MAILING_LIST_MULTI, RDNS_NONE,SPF_HELO_NONE,T_SCC_BODY_TEXT_LINE autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Wedson Almeida Filho A port to Rust of the PrimeCell PL061 GPIO driver. This module is a work in progress and will be sent for review later on, as well as separately from the Rust support. However, it is included to show how an actual working module written in Rust may look like. Signed-off-by: Wedson Almeida Filho Signed-off-by: Miguel Ojeda --- drivers/gpio/Kconfig | 8 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio_pl061_rust.rs | 370 ++++++++++++++++++++++++++++++++ 3 files changed, 379 insertions(+) create mode 100644 drivers/gpio/gpio_pl061_rust.rs diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 45764ec3b2eb..ad99b96f6d79 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -481,6 +481,14 @@ config GPIO_PL061 help Say yes here to support the PrimeCell PL061 GPIO device. +config GPIO_PL061_RUST + tristate "PrimeCell PL061 GPIO support written in Rust" + depends on ARM_AMBA && RUST + select IRQ_DOMAIN + select GPIOLIB_IRQCHIP + help + Say yes here to support the PrimeCell PL061 GPIO device + config GPIO_PMIC_EIC_SPRD tristate "Spreadtrum PMIC EIC support" depends on MFD_SC27XX_PMIC || COMPILE_TEST diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 14352f6dfe8e..30141fec12be 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -118,6 +118,7 @@ obj-$(CONFIG_GPIO_PCIE_IDIO_24) += gpio-pcie-idio-24.o obj-$(CONFIG_GPIO_PCI_IDIO_16) += gpio-pci-idio-16.o obj-$(CONFIG_GPIO_PISOSR) += gpio-pisosr.o obj-$(CONFIG_GPIO_PL061) += gpio-pl061.o +obj-$(CONFIG_GPIO_PL061_RUST) += gpio_pl061_rust.o obj-$(CONFIG_GPIO_PMIC_EIC_SPRD) += gpio-pmic-eic-sprd.o obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o obj-$(CONFIG_GPIO_RASPBERRYPI_EXP) += gpio-raspberrypi-exp.o diff --git a/drivers/gpio/gpio_pl061_rust.rs b/drivers/gpio/gpio_pl061_rust.rs new file mode 100644 index 000000000000..908914080442 --- /dev/null +++ b/drivers/gpio/gpio_pl061_rust.rs @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Driver for the ARM PrimeCell(tm) General Purpose Input/Output (PL061). +//! +//! Based on the C driver written by Baruch Siach . + +use kernel::{ + amba, bit, bits_iter, define_amba_id_table, device, gpio, + io_mem::IoMem, + irq::{self, ExtraResult, IrqData, LockedIrqData}, + power, + prelude::*, + sync::{RawSpinLock, Ref, RefBorrow}, +}; + +const GPIODIR: usize = 0x400; +const GPIOIS: usize = 0x404; +const GPIOIBE: usize = 0x408; +const GPIOIEV: usize = 0x40C; +const GPIOIE: usize = 0x410; +const GPIOMIS: usize = 0x418; +const GPIOIC: usize = 0x41C; +const GPIO_SIZE: usize = 0x1000; + +const PL061_GPIO_NR: u16 = 8; + +#[derive(Default)] +struct ContextSaveRegs { + gpio_data: u8, + gpio_dir: u8, + gpio_is: u8, + gpio_ibe: u8, + gpio_iev: u8, + gpio_ie: u8, +} + +#[derive(Default)] +struct PL061DataInner { + csave_regs: ContextSaveRegs, +} + +struct PL061Data { + dev: device::Device, + inner: RawSpinLock, +} + +struct PL061Resources { + base: IoMem, + parent_irq: u32, +} + +type PL061Registrations = gpio::RegistrationWithIrqChip; + +type DeviceData = device::Data; + +struct PL061Device; + +impl gpio::Chip for PL061Device { + type Data = Ref; + + kernel::declare_gpio_chip_operations!( + get_direction, + direction_input, + direction_output, + get, + set + ); + + fn get_direction(data: RefBorrow<'_, DeviceData>, offset: u32) -> Result { + let pl061 = data.resources().ok_or(ENXIO)?; + Ok(if pl061.base.readb(GPIODIR) & bit(offset) != 0 { + gpio::LineDirection::Out + } else { + gpio::LineDirection::In + }) + } + + fn direction_input(data: RefBorrow<'_, DeviceData>, offset: u32) -> Result { + let _guard = data.inner.lock_irqdisable(); + let pl061 = data.resources().ok_or(ENXIO)?; + let mut gpiodir = pl061.base.readb(GPIODIR); + gpiodir &= !bit(offset); + pl061.base.writeb(gpiodir, GPIODIR); + Ok(()) + } + + fn direction_output(data: RefBorrow<'_, DeviceData>, offset: u32, value: bool) -> Result { + let woffset = bit(offset + 2).into(); + let _guard = data.inner.lock_irqdisable(); + let pl061 = data.resources().ok_or(ENXIO)?; + pl061.base.try_writeb((value as u8) << offset, woffset)?; + let mut gpiodir = pl061.base.readb(GPIODIR); + gpiodir |= bit(offset); + pl061.base.writeb(gpiodir, GPIODIR); + + // gpio value is set again, because pl061 doesn't allow to set value of a gpio pin before + // configuring it in OUT mode. + pl061.base.try_writeb((value as u8) << offset, woffset)?; + Ok(()) + } + + fn get(data: RefBorrow<'_, DeviceData>, offset: u32) -> Result { + let pl061 = data.resources().ok_or(ENXIO)?; + Ok(pl061.base.try_readb(bit(offset + 2).into())? != 0) + } + + fn set(data: RefBorrow<'_, DeviceData>, offset: u32, value: bool) { + if let Some(pl061) = data.resources() { + let woffset = bit(offset + 2).into(); + let _ = pl061.base.try_writeb((value as u8) << offset, woffset); + } + } +} + +impl gpio::ChipWithIrqChip for PL061Device { + fn handle_irq_flow( + data: RefBorrow<'_, DeviceData>, + desc: &irq::Descriptor, + domain: &irq::Domain, + ) { + let chained = desc.enter_chained(); + + if let Some(pl061) = data.resources() { + let pending = pl061.base.readb(GPIOMIS); + for offset in bits_iter(pending) { + domain.generic_handle_chained(offset, &chained); + } + } + } +} + +impl irq::Chip for PL061Device { + type Data = Ref; + + kernel::declare_irq_chip_operations!(set_type, set_wake); + + fn set_type( + data: RefBorrow<'_, DeviceData>, + irq_data: &mut LockedIrqData, + trigger: u32, + ) -> Result { + let offset = irq_data.hwirq(); + let bit = bit(offset); + + if offset >= PL061_GPIO_NR.into() { + return Err(EINVAL); + } + + if trigger & (irq::Type::LEVEL_HIGH | irq::Type::LEVEL_LOW) != 0 + && trigger & (irq::Type::EDGE_RISING | irq::Type::EDGE_FALLING) != 0 + { + dev_err!( + data.dev, + "trying to configure line {} for both level and edge detection, choose one!\n", + offset + ); + return Err(EINVAL); + } + + let _guard = data.inner.lock_irqdisable(); + let pl061 = data.resources().ok_or(ENXIO)?; + + let mut gpioiev = pl061.base.readb(GPIOIEV); + let mut gpiois = pl061.base.readb(GPIOIS); + let mut gpioibe = pl061.base.readb(GPIOIBE); + + if trigger & (irq::Type::LEVEL_HIGH | irq::Type::LEVEL_LOW) != 0 { + let polarity = trigger & irq::Type::LEVEL_HIGH != 0; + + // Disable edge detection. + gpioibe &= !bit; + // Enable level detection. + gpiois |= bit; + // Select polarity. + if polarity { + gpioiev |= bit; + } else { + gpioiev &= !bit; + } + irq_data.set_level_handler(); + dev_dbg!( + data.dev, + "line {}: IRQ on {} level\n", + offset, + if polarity { "HIGH" } else { "LOW" } + ); + } else if (trigger & irq::Type::EDGE_BOTH) == irq::Type::EDGE_BOTH { + // Disable level detection. + gpiois &= !bit; + // Select both edges, settings this makes GPIOEV be ignored. + gpioibe |= bit; + irq_data.set_edge_handler(); + dev_dbg!(data.dev, "line {}: IRQ on both edges\n", offset); + } else if trigger & (irq::Type::EDGE_RISING | irq::Type::EDGE_FALLING) != 0 { + let rising = trigger & irq::Type::EDGE_RISING != 0; + + // Disable level detection. + gpiois &= !bit; + // Clear detection on both edges. + gpioibe &= !bit; + // Select edge. + if rising { + gpioiev |= bit; + } else { + gpioiev &= !bit; + } + irq_data.set_edge_handler(); + dev_dbg!( + data.dev, + "line {}: IRQ on {} edge\n", + offset, + if rising { "RISING" } else { "FALLING}" } + ); + } else { + // No trigger: disable everything. + gpiois &= !bit; + gpioibe &= !bit; + gpioiev &= !bit; + irq_data.set_bad_handler(); + dev_warn!(data.dev, "no trigger selected for line {}\n", offset); + } + + pl061.base.writeb(gpiois, GPIOIS); + pl061.base.writeb(gpioibe, GPIOIBE); + pl061.base.writeb(gpioiev, GPIOIEV); + + Ok(ExtraResult::None) + } + + fn mask(data: RefBorrow<'_, DeviceData>, irq_data: &IrqData) { + let mask = bit(irq_data.hwirq() % irq::HwNumber::from(PL061_GPIO_NR)); + let _guard = data.inner.lock(); + if let Some(pl061) = data.resources() { + let gpioie = pl061.base.readb(GPIOIE) & !mask; + pl061.base.writeb(gpioie, GPIOIE); + } + } + + fn unmask(data: RefBorrow<'_, DeviceData>, irq_data: &IrqData) { + let mask = bit(irq_data.hwirq() % irq::HwNumber::from(PL061_GPIO_NR)); + let _guard = data.inner.lock(); + if let Some(pl061) = data.resources() { + let gpioie = pl061.base.readb(GPIOIE) | mask; + pl061.base.writeb(gpioie, GPIOIE); + } + } + + // This gets called from the edge IRQ handler to ACK the edge IRQ in the GPIOIC + // (interrupt-clear) register. For level IRQs this is not needed: these go away when the level + // signal goes away. + fn ack(data: RefBorrow<'_, DeviceData>, irq_data: &IrqData) { + let mask = bit(irq_data.hwirq() % irq::HwNumber::from(PL061_GPIO_NR)); + let _guard = data.inner.lock(); + if let Some(pl061) = data.resources() { + pl061.base.writeb(mask.into(), GPIOIC); + } + } + + fn set_wake(data: RefBorrow<'_, DeviceData>, _irq_data: &IrqData, on: bool) -> Result { + let pl061 = data.resources().ok_or(ENXIO)?; + irq::set_wake(pl061.parent_irq, on) + } +} + +impl amba::Driver for PL061Device { + type Data = Ref; + type PowerOps = Self; + + define_amba_id_table! {(), [ + ({id: 0x00041061, mask: 0x000fffff}, None), + ]} + + fn probe(dev: &mut amba::Device, _data: Option<&Self::IdInfo>) -> Result> { + let res = dev.take_resource().ok_or(ENXIO)?; + let irq = dev.irq(0).ok_or(ENXIO)?; + + let mut data = kernel::new_device_data!( + gpio::RegistrationWithIrqChip::new(), + PL061Resources { + // SAFETY: This device doesn't support DMA. + base: unsafe { IoMem::try_new(res)? }, + parent_irq: irq, + }, + PL061Data { + dev: device::Device::from_dev(dev), + // SAFETY: We call `rawspinlock_init` below. + inner: unsafe { RawSpinLock::new(PL061DataInner::default()) }, + }, + "PL061::Registrations" + )?; + + // SAFETY: General part of the data is pinned when `data` is. + let gen_inner = unsafe { data.as_mut().map_unchecked_mut(|d| &mut (**d).inner) }; + kernel::rawspinlock_init!(gen_inner, "PL061Data::inner"); + + let data = Ref::::from(data); + + data.resources().ok_or(ENXIO)?.base.writeb(0, GPIOIE); // disable irqs + + data.registrations() + .ok_or(ENXIO)? + .as_pinned_mut() + .register::(PL061_GPIO_NR, None, dev, data.clone(), irq)?; + + dev_info!(data.dev, "PL061 GPIO chip registered\n"); + + Ok(data) + } +} + +impl power::Operations for PL061Device { + type Data = Ref; + + fn suspend(data: RefBorrow<'_, DeviceData>) -> Result { + let mut inner = data.inner.lock(); + let pl061 = data.resources().ok_or(ENXIO)?; + inner.csave_regs.gpio_data = 0; + inner.csave_regs.gpio_dir = pl061.base.readb(GPIODIR); + inner.csave_regs.gpio_is = pl061.base.readb(GPIOIS); + inner.csave_regs.gpio_ibe = pl061.base.readb(GPIOIBE); + inner.csave_regs.gpio_iev = pl061.base.readb(GPIOIEV); + inner.csave_regs.gpio_ie = pl061.base.readb(GPIOIE); + + for offset in 0..PL061_GPIO_NR { + if inner.csave_regs.gpio_dir & bit(offset) != 0 { + if let Ok(v) = ::get(data, offset.into()) { + inner.csave_regs.gpio_data |= (v as u8) << offset; + } + } + } + + Ok(()) + } + + fn resume(data: RefBorrow<'_, DeviceData>) -> Result { + let inner = data.inner.lock(); + let pl061 = data.resources().ok_or(ENXIO)?; + + for offset in 0..PL061_GPIO_NR { + if inner.csave_regs.gpio_dir & bit(offset) != 0 { + let value = inner.csave_regs.gpio_data & bit(offset) != 0; + let _ = ::direction_output(data, offset.into(), value); + } else { + let _ = ::direction_input(data, offset.into()); + } + } + + pl061.base.writeb(inner.csave_regs.gpio_is, GPIOIS); + pl061.base.writeb(inner.csave_regs.gpio_ibe, GPIOIBE); + pl061.base.writeb(inner.csave_regs.gpio_iev, GPIOIEV); + pl061.base.writeb(inner.csave_regs.gpio_ie, GPIOIE); + + Ok(()) + } + + fn freeze(data: RefBorrow<'_, DeviceData>) -> Result { + Self::suspend(data) + } + + fn restore(data: RefBorrow<'_, DeviceData>) -> Result { + Self::resume(data) + } +} + +module_amba_driver! { + type: PL061Device, + name: b"pl061_gpio", + author: b"Wedson Almeida Filho", + license: b"GPL", +} -- 2.36.1