mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

Add support for selecting multiple TLMM regions using the tlmm-test tool. The current implementation only selects the TLMM Node region 0, which can lead to incorrect region selection. QCS 615 TLMM Node dts reg: tlmm: pinctrl@3100000 { compatible = "qcom,qcs615-tlmm"; reg = <0x0 0x03100000 0x0 0x300000>, <0x0 0x03500000 0x0 0x300000>, <0x0 0x03d00000 0x0 0x300000>; reg-names = "east", "west", "south"; QCS615 gpio57 is in the south region with an offset of 0x39000, and its address is 0x3d39000. However, the default region selection is region 0 (east region), resulting in a wrong calculated address of 0x3139000. Add a tlmm option parameter named tlmm_reg_name to select the region. If the user does not input the parameter, the default region is 0. Signed-off-by: Yuanjie Yang <quic_yuanjiey@quicinc.com> Link: https://lore.kernel.org/20250624090600.91063-1-quic_yuanjiey@quicinc.com Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
709 lines
18 KiB
C
709 lines
18 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "tlmm-test: " fmt
|
|
|
|
#include <kunit/test.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
|
|
/*
|
|
* This TLMM test module serves the purpose of validating that the TLMM driver
|
|
* (pinctrl-msm) delivers expected number of interrupts in response to changing
|
|
* GPIO state.
|
|
*
|
|
* To achieve this without external equipment the test takes a module parameter
|
|
* "gpio", which the tester is expected to specify an unused and non-connected
|
|
* pin. The GPIO state is then driven by adjusting the bias of the pin, at
|
|
* suitable times through the different test cases.
|
|
*
|
|
* Upon execution, the test initialization will find the TLMM node (subject to
|
|
* tlmm_of_match[] allow listing) and create the necessary references
|
|
* dynamically, rather then relying on e.g. Devicetree and phandles.
|
|
*/
|
|
|
|
#define MSM_PULL_MASK GENMASK(2, 0)
|
|
#define MSM_PULL_DOWN 1
|
|
#define MSM_PULL_UP 3
|
|
#define TLMM_REG_SIZE 0x1000
|
|
|
|
static int tlmm_test_gpio = -1;
|
|
static char *tlmm_reg_name = "default_region";
|
|
|
|
module_param_named(gpio, tlmm_test_gpio, int, 0600);
|
|
module_param_named(name, tlmm_reg_name, charp, 0600);
|
|
|
|
static struct {
|
|
void __iomem *base;
|
|
void __iomem *reg;
|
|
int irq;
|
|
|
|
u32 low_val;
|
|
u32 high_val;
|
|
} tlmm_suite;
|
|
|
|
/**
|
|
* struct tlmm_test_priv - Per-test context
|
|
* @intr_count: number of times hard handler was hit with TLMM_TEST_COUNT op set
|
|
* @thread_count: number of times thread handler was hit with TLMM_TEST_COUNT op set
|
|
* @intr_op: operations to be performed by the hard IRQ handler
|
|
* @intr_op_remain: number of times the TLMM_TEST_THEN_* operations should be
|
|
* performed by the hard IRQ handler
|
|
* @thread_op: operations to be performed by the threaded IRQ handler
|
|
* @thread_op_remain: number of times the TLMM_TEST_THEN_* operations should
|
|
* be performed by the threaded IRQ handler
|
|
*/
|
|
struct tlmm_test_priv {
|
|
atomic_t intr_count;
|
|
atomic_t thread_count;
|
|
|
|
unsigned int intr_op;
|
|
atomic_t intr_op_remain;
|
|
|
|
unsigned int thread_op;
|
|
atomic_t thread_op_remain;
|
|
};
|
|
|
|
/* Operation masks for @intr_op and @thread_op */
|
|
#define TLMM_TEST_COUNT BIT(0)
|
|
#define TLMM_TEST_OUTPUT_LOW BIT(1)
|
|
#define TLMM_TEST_OUTPUT_HIGH BIT(2)
|
|
#define TLMM_TEST_THEN_HIGH BIT(3)
|
|
#define TLMM_TEST_THEN_LOW BIT(4)
|
|
#define TLMM_TEST_WAKE_THREAD BIT(5)
|
|
|
|
static void tlmm_output_low(void)
|
|
{
|
|
writel(tlmm_suite.low_val, tlmm_suite.reg);
|
|
readl(tlmm_suite.reg);
|
|
}
|
|
|
|
static void tlmm_output_high(void)
|
|
{
|
|
writel(tlmm_suite.high_val, tlmm_suite.reg);
|
|
readl(tlmm_suite.reg);
|
|
}
|
|
|
|
static irqreturn_t tlmm_test_intr_fn(int irq, void *dev_id)
|
|
{
|
|
struct tlmm_test_priv *priv = dev_id;
|
|
|
|
if (priv->intr_op & TLMM_TEST_COUNT)
|
|
atomic_inc(&priv->intr_count);
|
|
|
|
if (priv->intr_op & TLMM_TEST_OUTPUT_LOW)
|
|
tlmm_output_low();
|
|
if (priv->intr_op & TLMM_TEST_OUTPUT_HIGH)
|
|
tlmm_output_high();
|
|
|
|
if (atomic_dec_if_positive(&priv->intr_op_remain) > 0) {
|
|
udelay(1);
|
|
|
|
if (priv->intr_op & TLMM_TEST_THEN_LOW)
|
|
tlmm_output_low();
|
|
if (priv->intr_op & TLMM_TEST_THEN_HIGH)
|
|
tlmm_output_high();
|
|
}
|
|
|
|
return priv->intr_op & TLMM_TEST_WAKE_THREAD ? IRQ_WAKE_THREAD : IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t tlmm_test_intr_thread_fn(int irq, void *dev_id)
|
|
{
|
|
struct tlmm_test_priv *priv = dev_id;
|
|
|
|
if (priv->thread_op & TLMM_TEST_COUNT)
|
|
atomic_inc(&priv->thread_count);
|
|
|
|
if (priv->thread_op & TLMM_TEST_OUTPUT_LOW)
|
|
tlmm_output_low();
|
|
if (priv->thread_op & TLMM_TEST_OUTPUT_HIGH)
|
|
tlmm_output_high();
|
|
|
|
if (atomic_dec_if_positive(&priv->thread_op_remain) > 0) {
|
|
udelay(1);
|
|
if (priv->thread_op & TLMM_TEST_THEN_LOW)
|
|
tlmm_output_low();
|
|
if (priv->thread_op & TLMM_TEST_THEN_HIGH)
|
|
tlmm_output_high();
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void tlmm_test_request_hard_irq(struct kunit *test, unsigned long irqflags)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
int ret;
|
|
|
|
ret = request_irq(tlmm_suite.irq, tlmm_test_intr_fn, irqflags, test->name, priv);
|
|
KUNIT_EXPECT_EQ(test, ret, 0);
|
|
}
|
|
|
|
static void tlmm_test_request_threaded_irq(struct kunit *test, unsigned long irqflags)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
int ret;
|
|
|
|
ret = request_threaded_irq(tlmm_suite.irq,
|
|
tlmm_test_intr_fn, tlmm_test_intr_thread_fn,
|
|
irqflags, test->name, priv);
|
|
|
|
KUNIT_EXPECT_EQ(test, ret, 0);
|
|
}
|
|
|
|
static void tlmm_test_silent(struct kunit *test, unsigned long irqflags)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
|
|
priv->intr_op = TLMM_TEST_COUNT;
|
|
|
|
/* GPIO line at non-triggering level */
|
|
if (irqflags == IRQF_TRIGGER_LOW || irqflags == IRQF_TRIGGER_FALLING)
|
|
tlmm_output_high();
|
|
else
|
|
tlmm_output_low();
|
|
|
|
tlmm_test_request_hard_irq(test, irqflags);
|
|
msleep(100);
|
|
free_irq(tlmm_suite.irq, priv);
|
|
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->intr_count), 0);
|
|
}
|
|
|
|
/*
|
|
* Test that no RISING interrupts are triggered on a silent pin
|
|
*/
|
|
static void tlmm_test_silent_rising(struct kunit *test)
|
|
{
|
|
tlmm_test_silent(test, IRQF_TRIGGER_RISING);
|
|
}
|
|
|
|
/*
|
|
* Test that no FALLING interrupts are triggered on a silent pin
|
|
*/
|
|
static void tlmm_test_silent_falling(struct kunit *test)
|
|
{
|
|
tlmm_test_silent(test, IRQF_TRIGGER_FALLING);
|
|
}
|
|
|
|
/*
|
|
* Test that no LOW interrupts are triggered on a silent pin
|
|
*/
|
|
static void tlmm_test_silent_low(struct kunit *test)
|
|
{
|
|
tlmm_test_silent(test, IRQF_TRIGGER_LOW);
|
|
}
|
|
|
|
/*
|
|
* Test that no HIGH interrupts are triggered on a silent pin
|
|
*/
|
|
static void tlmm_test_silent_high(struct kunit *test)
|
|
{
|
|
tlmm_test_silent(test, IRQF_TRIGGER_HIGH);
|
|
}
|
|
|
|
/*
|
|
* Square wave with 10 high pulses, assert that we get 10 rising interrupts
|
|
*/
|
|
static void tlmm_test_rising(struct kunit *test)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
int i;
|
|
|
|
priv->intr_op = TLMM_TEST_COUNT;
|
|
|
|
tlmm_output_low();
|
|
|
|
tlmm_test_request_hard_irq(test, IRQF_TRIGGER_RISING);
|
|
for (i = 0; i < 10; i++) {
|
|
tlmm_output_low();
|
|
msleep(20);
|
|
tlmm_output_high();
|
|
msleep(20);
|
|
}
|
|
|
|
free_irq(tlmm_suite.irq, priv);
|
|
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->intr_count), 10);
|
|
}
|
|
|
|
/*
|
|
* Square wave with 10 low pulses, assert that we get 10 falling interrupts
|
|
*/
|
|
static void tlmm_test_falling(struct kunit *test)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
int i;
|
|
|
|
priv->intr_op = TLMM_TEST_COUNT;
|
|
|
|
tlmm_output_high();
|
|
|
|
tlmm_test_request_hard_irq(test, IRQF_TRIGGER_FALLING);
|
|
for (i = 0; i < 10; i++) {
|
|
tlmm_output_high();
|
|
msleep(20);
|
|
tlmm_output_low();
|
|
msleep(20);
|
|
}
|
|
free_irq(tlmm_suite.irq, priv);
|
|
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->intr_count), 10);
|
|
}
|
|
|
|
/*
|
|
* Drive line low 10 times, handler drives it high to "clear the interrupt
|
|
* source", assert we get 10 interrupts
|
|
*/
|
|
static void tlmm_test_low(struct kunit *test)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
int i;
|
|
|
|
priv->intr_op = TLMM_TEST_COUNT | TLMM_TEST_OUTPUT_HIGH;
|
|
atomic_set(&priv->intr_op_remain, 9);
|
|
|
|
tlmm_output_high();
|
|
|
|
tlmm_test_request_hard_irq(test, IRQF_TRIGGER_LOW);
|
|
for (i = 0; i < 10; i++) {
|
|
msleep(20);
|
|
tlmm_output_low();
|
|
}
|
|
msleep(100);
|
|
free_irq(tlmm_suite.irq, priv);
|
|
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->intr_count), 10);
|
|
}
|
|
|
|
/*
|
|
* Drive line high 10 times, handler drives it low to "clear the interrupt
|
|
* source", assert we get 10 interrupts
|
|
*/
|
|
static void tlmm_test_high(struct kunit *test)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
int i;
|
|
|
|
priv->intr_op = TLMM_TEST_COUNT | TLMM_TEST_OUTPUT_LOW;
|
|
atomic_set(&priv->intr_op_remain, 9);
|
|
|
|
tlmm_output_low();
|
|
|
|
tlmm_test_request_hard_irq(test, IRQF_TRIGGER_HIGH);
|
|
for (i = 0; i < 10; i++) {
|
|
msleep(20);
|
|
tlmm_output_high();
|
|
}
|
|
msleep(100);
|
|
free_irq(tlmm_suite.irq, priv);
|
|
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->intr_count), 10);
|
|
}
|
|
|
|
/*
|
|
* Handler drives GPIO high to "clear the interrupt source", then low to
|
|
* simulate a new interrupt, repeated 10 times, assert we get 10 interrupts
|
|
*/
|
|
static void tlmm_test_falling_in_handler(struct kunit *test)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
|
|
priv->intr_op = TLMM_TEST_COUNT | TLMM_TEST_OUTPUT_HIGH | TLMM_TEST_THEN_LOW;
|
|
atomic_set(&priv->intr_op_remain, 10);
|
|
|
|
tlmm_output_high();
|
|
|
|
tlmm_test_request_hard_irq(test, IRQF_TRIGGER_FALLING);
|
|
msleep(20);
|
|
tlmm_output_low();
|
|
msleep(100);
|
|
free_irq(tlmm_suite.irq, priv);
|
|
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->intr_count), 10);
|
|
}
|
|
|
|
/*
|
|
* Handler drives GPIO low to "clear the interrupt source", then high to
|
|
* simulate a new interrupt, repeated 10 times, assert we get 10 interrupts
|
|
*/
|
|
static void tlmm_test_rising_in_handler(struct kunit *test)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
|
|
priv->intr_op = TLMM_TEST_COUNT | TLMM_TEST_OUTPUT_LOW | TLMM_TEST_THEN_HIGH;
|
|
atomic_set(&priv->intr_op_remain, 10);
|
|
|
|
tlmm_output_low();
|
|
|
|
tlmm_test_request_hard_irq(test, IRQF_TRIGGER_RISING);
|
|
msleep(20);
|
|
tlmm_output_high();
|
|
msleep(100);
|
|
free_irq(tlmm_suite.irq, priv);
|
|
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->intr_count), 10);
|
|
}
|
|
|
|
/*
|
|
* Square wave with 10 high pulses, assert that we get 10 rising hard and
|
|
* 10 threaded interrupts
|
|
*/
|
|
static void tlmm_test_thread_rising(struct kunit *test)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
int i;
|
|
|
|
priv->intr_op = TLMM_TEST_COUNT | TLMM_TEST_WAKE_THREAD;
|
|
priv->thread_op = TLMM_TEST_COUNT;
|
|
|
|
tlmm_output_low();
|
|
|
|
tlmm_test_request_threaded_irq(test, IRQF_TRIGGER_RISING);
|
|
for (i = 0; i < 10; i++) {
|
|
tlmm_output_low();
|
|
msleep(20);
|
|
tlmm_output_high();
|
|
msleep(20);
|
|
}
|
|
free_irq(tlmm_suite.irq, priv);
|
|
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->intr_count), 10);
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->thread_count), 10);
|
|
}
|
|
|
|
/*
|
|
* Square wave with 10 low pulses, assert that we get 10 falling interrupts
|
|
*/
|
|
static void tlmm_test_thread_falling(struct kunit *test)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
int i;
|
|
|
|
priv->intr_op = TLMM_TEST_COUNT | TLMM_TEST_WAKE_THREAD;
|
|
priv->thread_op = TLMM_TEST_COUNT;
|
|
|
|
tlmm_output_high();
|
|
|
|
tlmm_test_request_threaded_irq(test, IRQF_TRIGGER_FALLING);
|
|
for (i = 0; i < 10; i++) {
|
|
tlmm_output_high();
|
|
msleep(20);
|
|
tlmm_output_low();
|
|
msleep(20);
|
|
}
|
|
free_irq(tlmm_suite.irq, priv);
|
|
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->intr_count), 10);
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->thread_count), 10);
|
|
}
|
|
|
|
/*
|
|
* Drive line high 10 times, threaded handler drives it low to "clear the
|
|
* interrupt source", assert we get 10 interrupts
|
|
*/
|
|
static void tlmm_test_thread_high(struct kunit *test)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
int i;
|
|
|
|
priv->intr_op = TLMM_TEST_COUNT | TLMM_TEST_WAKE_THREAD;
|
|
priv->thread_op = TLMM_TEST_COUNT | TLMM_TEST_OUTPUT_LOW;
|
|
|
|
tlmm_output_low();
|
|
|
|
tlmm_test_request_threaded_irq(test, IRQF_TRIGGER_HIGH | IRQF_ONESHOT);
|
|
for (i = 0; i < 10; i++) {
|
|
tlmm_output_high();
|
|
msleep(20);
|
|
}
|
|
free_irq(tlmm_suite.irq, priv);
|
|
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->intr_count), 10);
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->thread_count), 10);
|
|
}
|
|
|
|
/*
|
|
* Drive line low 10 times, threaded handler drives it high to "clear the
|
|
* interrupt source", assert we get 10 interrupts
|
|
*/
|
|
static void tlmm_test_thread_low(struct kunit *test)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
int i;
|
|
|
|
priv->intr_op = TLMM_TEST_COUNT | TLMM_TEST_WAKE_THREAD;
|
|
priv->thread_op = TLMM_TEST_COUNT | TLMM_TEST_OUTPUT_HIGH;
|
|
|
|
tlmm_output_high();
|
|
|
|
tlmm_test_request_threaded_irq(test, IRQF_TRIGGER_LOW | IRQF_ONESHOT);
|
|
for (i = 0; i < 10; i++) {
|
|
tlmm_output_low();
|
|
msleep(20);
|
|
}
|
|
free_irq(tlmm_suite.irq, priv);
|
|
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->intr_count), 10);
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->thread_count), 10);
|
|
}
|
|
|
|
/*
|
|
* Handler drives GPIO low to "clear the interrupt source", then high in the
|
|
* threaded handler to simulate a new interrupt, repeated 10 times, assert we
|
|
* get 10 interrupts
|
|
*/
|
|
static void tlmm_test_thread_rising_in_handler(struct kunit *test)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
|
|
priv->intr_op = TLMM_TEST_COUNT | TLMM_TEST_OUTPUT_LOW | TLMM_TEST_WAKE_THREAD;
|
|
priv->thread_op = TLMM_TEST_COUNT | TLMM_TEST_THEN_HIGH;
|
|
atomic_set(&priv->thread_op_remain, 10);
|
|
|
|
tlmm_output_low();
|
|
|
|
tlmm_test_request_threaded_irq(test, IRQF_TRIGGER_RISING);
|
|
msleep(20);
|
|
tlmm_output_high();
|
|
msleep(100);
|
|
free_irq(tlmm_suite.irq, priv);
|
|
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->intr_count), 10);
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->thread_count), 10);
|
|
}
|
|
|
|
/*
|
|
* Handler drives GPIO high to "clear the interrupt source", then low in the
|
|
* threaded handler to simulate a new interrupt, repeated 10 times, assert we
|
|
* get 10 interrupts
|
|
*/
|
|
static void tlmm_test_thread_falling_in_handler(struct kunit *test)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
|
|
priv->intr_op = TLMM_TEST_COUNT | TLMM_TEST_OUTPUT_HIGH | TLMM_TEST_WAKE_THREAD;
|
|
priv->thread_op = TLMM_TEST_COUNT | TLMM_TEST_THEN_LOW;
|
|
atomic_set(&priv->thread_op_remain, 10);
|
|
|
|
tlmm_output_high();
|
|
|
|
tlmm_test_request_threaded_irq(test, IRQF_TRIGGER_FALLING);
|
|
msleep(20);
|
|
tlmm_output_low();
|
|
msleep(100);
|
|
free_irq(tlmm_suite.irq, priv);
|
|
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->intr_count), 10);
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->thread_count), 10);
|
|
}
|
|
|
|
/*
|
|
* Validate that edge interrupts occurring while irq is disabled is delivered
|
|
* once the interrupt is reenabled.
|
|
*/
|
|
static void tlmm_test_rising_while_disabled(struct kunit *test)
|
|
{
|
|
struct tlmm_test_priv *priv = test->priv;
|
|
unsigned int after_edge;
|
|
unsigned int before_edge;
|
|
|
|
priv->intr_op = TLMM_TEST_COUNT;
|
|
atomic_set(&priv->thread_op_remain, 10);
|
|
|
|
tlmm_output_low();
|
|
|
|
tlmm_test_request_hard_irq(test, IRQF_TRIGGER_RISING);
|
|
msleep(20);
|
|
|
|
disable_irq(tlmm_suite.irq);
|
|
before_edge = atomic_read(&priv->intr_count);
|
|
|
|
tlmm_output_high();
|
|
msleep(20);
|
|
after_edge = atomic_read(&priv->intr_count);
|
|
|
|
msleep(20);
|
|
enable_irq(tlmm_suite.irq);
|
|
msleep(20);
|
|
|
|
free_irq(tlmm_suite.irq, priv);
|
|
|
|
KUNIT_ASSERT_EQ(test, before_edge, 0);
|
|
KUNIT_ASSERT_EQ(test, after_edge, 0);
|
|
KUNIT_ASSERT_EQ(test, atomic_read(&priv->intr_count), 1);
|
|
}
|
|
|
|
static int tlmm_test_init(struct kunit *test)
|
|
{
|
|
struct tlmm_test_priv *priv;
|
|
|
|
priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
|
|
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
|
|
|
|
atomic_set(&priv->intr_count, 0);
|
|
atomic_set(&priv->thread_count, 0);
|
|
|
|
atomic_set(&priv->intr_op_remain, 0);
|
|
atomic_set(&priv->thread_op_remain, 0);
|
|
|
|
test->priv = priv;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* NOTE: When adding compatibles to this list, ensure that TLMM_REG_SIZE and
|
|
* pull configuration values are supported and correct.
|
|
*/
|
|
static const struct of_device_id tlmm_of_match[] = {
|
|
{ .compatible = "qcom,sc8280xp-tlmm" },
|
|
{ .compatible = "qcom,x1e80100-tlmm" },
|
|
{}
|
|
};
|
|
|
|
static int tlmm_reg_base(struct device_node *tlmm, struct resource *res)
|
|
{
|
|
const char **reg_names;
|
|
int count;
|
|
int ret;
|
|
int i;
|
|
|
|
count = of_property_count_strings(tlmm, "reg-names");
|
|
if (count <= 0) {
|
|
pr_err("failed to find tlmm reg name\n");
|
|
return count;
|
|
}
|
|
|
|
reg_names = kcalloc(count, sizeof(char *), GFP_KERNEL);
|
|
if (!reg_names)
|
|
return -ENOMEM;
|
|
|
|
ret = of_property_read_string_array(tlmm, "reg-names", reg_names, count);
|
|
if (ret != count) {
|
|
kfree(reg_names);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!strcmp(tlmm_reg_name, "default_region")) {
|
|
ret = of_address_to_resource(tlmm, 0, res);
|
|
} else {
|
|
for (i = 0; i < count; i++) {
|
|
if (!strcmp(reg_names[i], tlmm_reg_name)) {
|
|
ret = of_address_to_resource(tlmm, i, res);
|
|
break;
|
|
}
|
|
}
|
|
if (i == count)
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
kfree(reg_names);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tlmm_test_init_suite(struct kunit_suite *suite)
|
|
{
|
|
struct of_phandle_args args = {};
|
|
struct resource res;
|
|
int ret;
|
|
u32 val;
|
|
|
|
if (tlmm_test_gpio < 0) {
|
|
pr_err("use the tlmm-test.gpio module parameter to specify which GPIO to use\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
struct device_node *tlmm __free(device_node) = of_find_matching_node(NULL, tlmm_of_match);
|
|
if (!tlmm) {
|
|
pr_err("failed to find tlmm node\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = tlmm_reg_base(tlmm, &res);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
tlmm_suite.base = ioremap(res.start, resource_size(&res));
|
|
if (!tlmm_suite.base)
|
|
return -ENOMEM;
|
|
|
|
args.np = tlmm;
|
|
args.args_count = 2;
|
|
args.args[0] = tlmm_test_gpio;
|
|
args.args[1] = 0;
|
|
|
|
tlmm_suite.irq = irq_create_of_mapping(&args);
|
|
if (!tlmm_suite.irq) {
|
|
pr_err("failed to map TLMM irq %d\n", args.args[0]);
|
|
goto err_unmap;
|
|
}
|
|
|
|
tlmm_suite.reg = tlmm_suite.base + tlmm_test_gpio * TLMM_REG_SIZE;
|
|
val = readl(tlmm_suite.reg) & ~MSM_PULL_MASK;
|
|
tlmm_suite.low_val = val | MSM_PULL_DOWN;
|
|
tlmm_suite.high_val = val | MSM_PULL_UP;
|
|
|
|
return 0;
|
|
|
|
err_unmap:
|
|
iounmap(tlmm_suite.base);
|
|
tlmm_suite.base = NULL;
|
|
return -EINVAL;
|
|
}
|
|
|
|
static void tlmm_test_exit_suite(struct kunit_suite *suite)
|
|
{
|
|
irq_dispose_mapping(tlmm_suite.irq);
|
|
iounmap(tlmm_suite.base);
|
|
|
|
tlmm_suite.base = NULL;
|
|
tlmm_suite.irq = -1;
|
|
}
|
|
|
|
static struct kunit_case tlmm_test_cases[] = {
|
|
KUNIT_CASE(tlmm_test_silent_rising),
|
|
KUNIT_CASE(tlmm_test_silent_falling),
|
|
KUNIT_CASE(tlmm_test_silent_low),
|
|
KUNIT_CASE(tlmm_test_silent_high),
|
|
KUNIT_CASE(tlmm_test_rising),
|
|
KUNIT_CASE(tlmm_test_falling),
|
|
KUNIT_CASE(tlmm_test_high),
|
|
KUNIT_CASE(tlmm_test_low),
|
|
KUNIT_CASE(tlmm_test_rising_in_handler),
|
|
KUNIT_CASE(tlmm_test_falling_in_handler),
|
|
KUNIT_CASE(tlmm_test_thread_rising),
|
|
KUNIT_CASE(tlmm_test_thread_falling),
|
|
KUNIT_CASE(tlmm_test_thread_high),
|
|
KUNIT_CASE(tlmm_test_thread_low),
|
|
KUNIT_CASE(tlmm_test_thread_rising_in_handler),
|
|
KUNIT_CASE(tlmm_test_thread_falling_in_handler),
|
|
KUNIT_CASE(tlmm_test_rising_while_disabled),
|
|
{}
|
|
};
|
|
|
|
static struct kunit_suite tlmm_test_suite = {
|
|
.name = "tlmm-test",
|
|
.init = tlmm_test_init,
|
|
.suite_init = tlmm_test_init_suite,
|
|
.suite_exit = tlmm_test_exit_suite,
|
|
.test_cases = tlmm_test_cases,
|
|
};
|
|
|
|
kunit_test_suites(&tlmm_test_suite);
|
|
|
|
MODULE_DESCRIPTION("Qualcomm TLMM test");
|
|
MODULE_LICENSE("GPL");
|