mirror of https://github.com/torvalds/linux.git
rust: opp: Add abstractions for the configuration options
Introduce Rust abstractions for the OPP core configuration options, enabling safe access to various configurable aspects of the OPP framework. Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
This commit is contained in:
parent
d52c7e868f
commit
ce32e2d47c
|
|
@ -12,12 +12,29 @@
|
||||||
clk::Hertz,
|
clk::Hertz,
|
||||||
cpumask::{Cpumask, CpumaskVar},
|
cpumask::{Cpumask, CpumaskVar},
|
||||||
device::Device,
|
device::Device,
|
||||||
error::{code::*, from_err_ptr, to_result, Error, Result},
|
error::{code::*, from_err_ptr, from_result, to_result, Error, Result, VTABLE_DEFAULT_ERROR},
|
||||||
ffi::c_ulong,
|
ffi::c_ulong,
|
||||||
|
prelude::*,
|
||||||
|
str::CString,
|
||||||
types::{ARef, AlwaysRefCounted, Opaque},
|
types::{ARef, AlwaysRefCounted, Opaque},
|
||||||
};
|
};
|
||||||
|
|
||||||
use core::ptr;
|
use core::{marker::PhantomData, ptr};
|
||||||
|
|
||||||
|
use macros::vtable;
|
||||||
|
|
||||||
|
/// Creates a null-terminated slice of pointers to [`Cstring`]s.
|
||||||
|
fn to_c_str_array(names: &[CString]) -> Result<KVec<*const u8>> {
|
||||||
|
// Allocated a null-terminated vector of pointers.
|
||||||
|
let mut list = KVec::with_capacity(names.len() + 1, GFP_KERNEL)?;
|
||||||
|
|
||||||
|
for name in names.iter() {
|
||||||
|
list.push(name.as_ptr() as _, GFP_KERNEL)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.push(ptr::null(), GFP_KERNEL)?;
|
||||||
|
Ok(list)
|
||||||
|
}
|
||||||
|
|
||||||
/// The voltage unit.
|
/// The voltage unit.
|
||||||
///
|
///
|
||||||
|
|
@ -205,6 +222,280 @@ pub enum SearchType {
|
||||||
Ceil,
|
Ceil,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OPP configuration callbacks.
|
||||||
|
///
|
||||||
|
/// Implement this trait to customize OPP clock and regulator setup for your device.
|
||||||
|
#[vtable]
|
||||||
|
pub trait ConfigOps {
|
||||||
|
/// This is typically used to scale clocks when transitioning between OPPs.
|
||||||
|
#[inline]
|
||||||
|
fn config_clks(_dev: &Device, _table: &Table, _opp: &OPP, _scaling_down: bool) -> Result {
|
||||||
|
build_error!(VTABLE_DEFAULT_ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This provides access to the old and new OPPs, allowing for safe regulator adjustments.
|
||||||
|
#[inline]
|
||||||
|
fn config_regulators(
|
||||||
|
_dev: &Device,
|
||||||
|
_opp_old: &OPP,
|
||||||
|
_opp_new: &OPP,
|
||||||
|
_data: *mut *mut bindings::regulator,
|
||||||
|
_count: u32,
|
||||||
|
) -> Result {
|
||||||
|
build_error!(VTABLE_DEFAULT_ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// OPP configuration token.
|
||||||
|
///
|
||||||
|
/// Returned by the OPP core when configuration is applied to a [`Device`]. The associated
|
||||||
|
/// configuration is automatically cleared when the token is dropped.
|
||||||
|
pub struct ConfigToken(i32);
|
||||||
|
|
||||||
|
impl Drop for ConfigToken {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// SAFETY: This is the same token value returned by the C code via `dev_pm_opp_set_config`.
|
||||||
|
unsafe { bindings::dev_pm_opp_clear_config(self.0) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// OPP configurations.
|
||||||
|
///
|
||||||
|
/// Rust abstraction for the C `struct dev_pm_opp_config`.
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
///
|
||||||
|
/// The following example demonstrates how to set OPP property-name configuration for a [`Device`].
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use kernel::device::Device;
|
||||||
|
/// use kernel::error::Result;
|
||||||
|
/// use kernel::opp::{Config, ConfigOps, ConfigToken};
|
||||||
|
/// use kernel::str::CString;
|
||||||
|
/// use kernel::types::ARef;
|
||||||
|
/// use kernel::macros::vtable;
|
||||||
|
///
|
||||||
|
/// #[derive(Default)]
|
||||||
|
/// struct Driver;
|
||||||
|
///
|
||||||
|
/// #[vtable]
|
||||||
|
/// impl ConfigOps for Driver {}
|
||||||
|
///
|
||||||
|
/// fn configure(dev: &ARef<Device>) -> Result<ConfigToken> {
|
||||||
|
/// let name = CString::try_from_fmt(fmt!("{}", "slow"))?;
|
||||||
|
///
|
||||||
|
/// // The OPP configuration is cleared once the [`ConfigToken`] goes out of scope.
|
||||||
|
/// Config::<Driver>::new()
|
||||||
|
/// .set_prop_name(name)?
|
||||||
|
/// .set(dev)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Config<T: ConfigOps>
|
||||||
|
where
|
||||||
|
T: Default,
|
||||||
|
{
|
||||||
|
clk_names: Option<KVec<CString>>,
|
||||||
|
prop_name: Option<CString>,
|
||||||
|
regulator_names: Option<KVec<CString>>,
|
||||||
|
supported_hw: Option<KVec<u32>>,
|
||||||
|
|
||||||
|
// Tuple containing (required device, index)
|
||||||
|
required_dev: Option<(ARef<Device>, u32)>,
|
||||||
|
_data: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ConfigOps + Default> Config<T> {
|
||||||
|
/// Creates a new instance of [`Config`].
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes clock names.
|
||||||
|
pub fn set_clk_names(mut self, names: KVec<CString>) -> Result<Self> {
|
||||||
|
if self.clk_names.is_some() {
|
||||||
|
return Err(EBUSY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if names.is_empty() {
|
||||||
|
return Err(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.clk_names = Some(names);
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes property name.
|
||||||
|
pub fn set_prop_name(mut self, name: CString) -> Result<Self> {
|
||||||
|
if self.prop_name.is_some() {
|
||||||
|
return Err(EBUSY);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.prop_name = Some(name);
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes regulator names.
|
||||||
|
pub fn set_regulator_names(mut self, names: KVec<CString>) -> Result<Self> {
|
||||||
|
if self.regulator_names.is_some() {
|
||||||
|
return Err(EBUSY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if names.is_empty() {
|
||||||
|
return Err(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.regulator_names = Some(names);
|
||||||
|
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes required devices.
|
||||||
|
pub fn set_required_dev(mut self, dev: ARef<Device>, index: u32) -> Result<Self> {
|
||||||
|
if self.required_dev.is_some() {
|
||||||
|
return Err(EBUSY);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.required_dev = Some((dev, index));
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes supported hardware.
|
||||||
|
pub fn set_supported_hw(mut self, hw: KVec<u32>) -> Result<Self> {
|
||||||
|
if self.supported_hw.is_some() {
|
||||||
|
return Err(EBUSY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if hw.is_empty() {
|
||||||
|
return Err(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.supported_hw = Some(hw);
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the configuration with the OPP core.
|
||||||
|
///
|
||||||
|
/// The returned [`ConfigToken`] will remove the configuration when dropped.
|
||||||
|
pub fn set(self, dev: &Device) -> Result<ConfigToken> {
|
||||||
|
let (_clk_list, clk_names) = match &self.clk_names {
|
||||||
|
Some(x) => {
|
||||||
|
let list = to_c_str_array(x)?;
|
||||||
|
let ptr = list.as_ptr();
|
||||||
|
(Some(list), ptr)
|
||||||
|
}
|
||||||
|
None => (None, ptr::null()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (_regulator_list, regulator_names) = match &self.regulator_names {
|
||||||
|
Some(x) => {
|
||||||
|
let list = to_c_str_array(x)?;
|
||||||
|
let ptr = list.as_ptr();
|
||||||
|
(Some(list), ptr)
|
||||||
|
}
|
||||||
|
None => (None, ptr::null()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let prop_name = self
|
||||||
|
.prop_name
|
||||||
|
.as_ref()
|
||||||
|
.map_or(ptr::null(), |p| p.as_char_ptr());
|
||||||
|
|
||||||
|
let (supported_hw, supported_hw_count) = self
|
||||||
|
.supported_hw
|
||||||
|
.as_ref()
|
||||||
|
.map_or((ptr::null(), 0), |hw| (hw.as_ptr(), hw.len() as u32));
|
||||||
|
|
||||||
|
let (required_dev, required_dev_index) = self
|
||||||
|
.required_dev
|
||||||
|
.as_ref()
|
||||||
|
.map_or((ptr::null_mut(), 0), |(dev, idx)| (dev.as_raw(), *idx));
|
||||||
|
|
||||||
|
let mut config = bindings::dev_pm_opp_config {
|
||||||
|
clk_names,
|
||||||
|
config_clks: if T::HAS_CONFIG_CLKS {
|
||||||
|
Some(Self::config_clks)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
prop_name,
|
||||||
|
regulator_names,
|
||||||
|
config_regulators: if T::HAS_CONFIG_REGULATORS {
|
||||||
|
Some(Self::config_regulators)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
supported_hw,
|
||||||
|
supported_hw_count,
|
||||||
|
|
||||||
|
required_dev,
|
||||||
|
required_dev_index,
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: The requirements are satisfied by the existence of [`Device`] and its safety
|
||||||
|
// requirements. The OPP core guarantees not to access fields of [`Config`] after this call
|
||||||
|
// and so we don't need to save a copy of them for future use.
|
||||||
|
let ret = unsafe { bindings::dev_pm_opp_set_config(dev.as_raw(), &mut config) };
|
||||||
|
if ret < 0 {
|
||||||
|
Err(Error::from_errno(ret))
|
||||||
|
} else {
|
||||||
|
Ok(ConfigToken(ret))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Config's clk callback.
|
||||||
|
///
|
||||||
|
/// SAFETY: Called from C. Inputs must be valid pointers.
|
||||||
|
extern "C" fn config_clks(
|
||||||
|
dev: *mut bindings::device,
|
||||||
|
opp_table: *mut bindings::opp_table,
|
||||||
|
opp: *mut bindings::dev_pm_opp,
|
||||||
|
_data: *mut kernel::ffi::c_void,
|
||||||
|
scaling_down: bool,
|
||||||
|
) -> kernel::ffi::c_int {
|
||||||
|
from_result(|| {
|
||||||
|
// SAFETY: 'dev' is guaranteed by the C code to be valid.
|
||||||
|
let dev = unsafe { Device::get_device(dev) };
|
||||||
|
T::config_clks(
|
||||||
|
&dev,
|
||||||
|
// SAFETY: 'opp_table' is guaranteed by the C code to be valid.
|
||||||
|
&unsafe { Table::from_raw_table(opp_table, &dev) },
|
||||||
|
// SAFETY: 'opp' is guaranteed by the C code to be valid.
|
||||||
|
unsafe { OPP::from_raw_opp(opp)? },
|
||||||
|
scaling_down,
|
||||||
|
)
|
||||||
|
.map(|()| 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Config's regulator callback.
|
||||||
|
///
|
||||||
|
/// SAFETY: Called from C. Inputs must be valid pointers.
|
||||||
|
extern "C" fn config_regulators(
|
||||||
|
dev: *mut bindings::device,
|
||||||
|
old_opp: *mut bindings::dev_pm_opp,
|
||||||
|
new_opp: *mut bindings::dev_pm_opp,
|
||||||
|
regulators: *mut *mut bindings::regulator,
|
||||||
|
count: kernel::ffi::c_uint,
|
||||||
|
) -> kernel::ffi::c_int {
|
||||||
|
from_result(|| {
|
||||||
|
// SAFETY: 'dev' is guaranteed by the C code to be valid.
|
||||||
|
let dev = unsafe { Device::get_device(dev) };
|
||||||
|
T::config_regulators(
|
||||||
|
&dev,
|
||||||
|
// SAFETY: 'old_opp' is guaranteed by the C code to be valid.
|
||||||
|
unsafe { OPP::from_raw_opp(old_opp)? },
|
||||||
|
// SAFETY: 'new_opp' is guaranteed by the C code to be valid.
|
||||||
|
unsafe { OPP::from_raw_opp(new_opp)? },
|
||||||
|
regulators,
|
||||||
|
count,
|
||||||
|
)
|
||||||
|
.map(|()| 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A reference-counted OPP table.
|
/// A reference-counted OPP table.
|
||||||
///
|
///
|
||||||
/// Rust abstraction for the C `struct opp_table`.
|
/// Rust abstraction for the C `struct opp_table`.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue