| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2022 MediaTek Inc. |
| * Author Fengping Yu <fengping.yu@mediatek.com> |
| */ |
| #include <linux/bitops.h> |
| #include <linux/clk.h> |
| #include <linux/input.h> |
| #include <linux/input/matrix_keypad.h> |
| #include <linux/interrupt.h> |
| #include <linux/module.h> |
| #include <linux/property.h> |
| #include <linux/platform_device.h> |
| #include <linux/regmap.h> |
| |
| #define MTK_KPD_NAME "mt6779-keypad" |
| #define MTK_KPD_MEM 0x0004 |
| #define MTK_KPD_DEBOUNCE 0x0018 |
| #define MTK_KPD_DEBOUNCE_MASK GENMASK(13, 0) |
| #define MTK_KPD_DEBOUNCE_MAX_MS 256 |
| #define MTK_KPD_SEL 0x0020 |
| #define MTK_KPD_SEL_DOUBLE_KP_MODE BIT(0) |
| #define MTK_KPD_SEL_COL GENMASK(15, 10) |
| #define MTK_KPD_SEL_ROW GENMASK(9, 4) |
| #define MTK_KPD_SEL_COLMASK(c) GENMASK((c) + 9, 10) |
| #define MTK_KPD_SEL_ROWMASK(r) GENMASK((r) + 3, 4) |
| #define MTK_KPD_NUM_MEMS 5 |
| #define MTK_KPD_NUM_BITS 136 /* 4*32+8 MEM5 only use 8 BITS */ |
| |
| struct mt6779_keypad { |
| struct regmap *regmap; |
| struct input_dev *input_dev; |
| struct clk *clk; |
| u32 n_rows; |
| u32 n_cols; |
| void (*calc_row_col)(unsigned int key, |
| unsigned int *row, unsigned int *col); |
| DECLARE_BITMAP(keymap_state, MTK_KPD_NUM_BITS); |
| }; |
| |
| static const struct regmap_config mt6779_keypad_regmap_cfg = { |
| .reg_bits = 32, |
| .val_bits = 32, |
| .reg_stride = sizeof(u32), |
| .max_register = 36, |
| }; |
| |
| static irqreturn_t mt6779_keypad_irq_handler(int irq, void *dev_id) |
| { |
| struct mt6779_keypad *keypad = dev_id; |
| const unsigned short *keycode = keypad->input_dev->keycode; |
| DECLARE_BITMAP(new_state, MTK_KPD_NUM_BITS); |
| DECLARE_BITMAP(change, MTK_KPD_NUM_BITS); |
| unsigned int bit_nr, key; |
| unsigned int row, col; |
| unsigned int scancode; |
| unsigned int row_shift = get_count_order(keypad->n_cols); |
| bool pressed; |
| |
| regmap_bulk_read(keypad->regmap, MTK_KPD_MEM, |
| new_state, MTK_KPD_NUM_MEMS); |
| |
| bitmap_xor(change, new_state, keypad->keymap_state, MTK_KPD_NUM_BITS); |
| |
| for_each_set_bit(bit_nr, change, MTK_KPD_NUM_BITS) { |
| /* |
| * Registers are 32bits, but only bits [15:0] are used to |
| * indicate key status. |
| */ |
| if (bit_nr % 32 >= 16) |
| continue; |
| |
| key = bit_nr / 32 * 16 + bit_nr % 32; |
| keypad->calc_row_col(key, &row, &col); |
| |
| scancode = MATRIX_SCAN_CODE(row, col, row_shift); |
| /* 1: not pressed, 0: pressed */ |
| pressed = !test_bit(bit_nr, new_state); |
| dev_dbg(&keypad->input_dev->dev, "%s", |
| pressed ? "pressed" : "released"); |
| |
| input_event(keypad->input_dev, EV_MSC, MSC_SCAN, scancode); |
| input_report_key(keypad->input_dev, keycode[scancode], pressed); |
| input_sync(keypad->input_dev); |
| |
| dev_dbg(&keypad->input_dev->dev, |
| "report Linux keycode = %d\n", keycode[scancode]); |
| } |
| |
| bitmap_copy(keypad->keymap_state, new_state, MTK_KPD_NUM_BITS); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void mt6779_keypad_calc_row_col_single(unsigned int key, |
| unsigned int *row, |
| unsigned int *col) |
| { |
| *row = key / 9; |
| *col = key % 9; |
| } |
| |
| static void mt6779_keypad_calc_row_col_double(unsigned int key, |
| unsigned int *row, |
| unsigned int *col) |
| { |
| *row = key / 13; |
| *col = (key % 13) / 2; |
| } |
| |
| static int mt6779_keypad_pdrv_probe(struct platform_device *pdev) |
| { |
| struct mt6779_keypad *keypad; |
| void __iomem *base; |
| int irq; |
| u32 debounce; |
| u32 keys_per_group; |
| bool wakeup; |
| int error; |
| |
| keypad = devm_kzalloc(&pdev->dev, sizeof(*keypad), GFP_KERNEL); |
| if (!keypad) |
| return -ENOMEM; |
| |
| base = devm_platform_ioremap_resource(pdev, 0); |
| if (IS_ERR(base)) |
| return PTR_ERR(base); |
| |
| keypad->regmap = devm_regmap_init_mmio(&pdev->dev, base, |
| &mt6779_keypad_regmap_cfg); |
| if (IS_ERR(keypad->regmap)) { |
| dev_err(&pdev->dev, |
| "regmap init failed:%pe\n", keypad->regmap); |
| return PTR_ERR(keypad->regmap); |
| } |
| |
| bitmap_fill(keypad->keymap_state, MTK_KPD_NUM_BITS); |
| |
| keypad->input_dev = devm_input_allocate_device(&pdev->dev); |
| if (!keypad->input_dev) { |
| dev_err(&pdev->dev, "Failed to allocate input dev\n"); |
| return -ENOMEM; |
| } |
| |
| keypad->input_dev->name = MTK_KPD_NAME; |
| keypad->input_dev->id.bustype = BUS_HOST; |
| |
| error = matrix_keypad_parse_properties(&pdev->dev, &keypad->n_rows, |
| &keypad->n_cols); |
| if (error) { |
| dev_err(&pdev->dev, "Failed to parse keypad params\n"); |
| return error; |
| } |
| |
| if (device_property_read_u32(&pdev->dev, "debounce-delay-ms", |
| &debounce)) |
| debounce = 16; |
| |
| if (debounce > MTK_KPD_DEBOUNCE_MAX_MS) { |
| dev_err(&pdev->dev, |
| "Debounce time exceeds the maximum allowed time %dms\n", |
| MTK_KPD_DEBOUNCE_MAX_MS); |
| return -EINVAL; |
| } |
| |
| if (device_property_read_u32(&pdev->dev, "mediatek,keys-per-group", |
| &keys_per_group)) |
| keys_per_group = 1; |
| |
| switch (keys_per_group) { |
| case 1: |
| keypad->calc_row_col = mt6779_keypad_calc_row_col_single; |
| break; |
| case 2: |
| keypad->calc_row_col = mt6779_keypad_calc_row_col_double; |
| break; |
| default: |
| dev_err(&pdev->dev, |
| "Invalid keys-per-group: %d\n", keys_per_group); |
| return -EINVAL; |
| } |
| |
| wakeup = device_property_read_bool(&pdev->dev, "wakeup-source"); |
| |
| dev_dbg(&pdev->dev, "n_row=%d n_col=%d debounce=%d\n", |
| keypad->n_rows, keypad->n_cols, debounce); |
| |
| error = matrix_keypad_build_keymap(NULL, NULL, |
| keypad->n_rows, keypad->n_cols, |
| NULL, keypad->input_dev); |
| if (error) { |
| dev_err(&pdev->dev, "Failed to build keymap\n"); |
| return error; |
| } |
| |
| input_set_capability(keypad->input_dev, EV_MSC, MSC_SCAN); |
| |
| regmap_write(keypad->regmap, MTK_KPD_DEBOUNCE, |
| (debounce * (1 << 5)) & MTK_KPD_DEBOUNCE_MASK); |
| |
| if (keys_per_group == 2) |
| regmap_update_bits(keypad->regmap, MTK_KPD_SEL, |
| MTK_KPD_SEL_DOUBLE_KP_MODE, |
| MTK_KPD_SEL_DOUBLE_KP_MODE); |
| |
| regmap_update_bits(keypad->regmap, MTK_KPD_SEL, MTK_KPD_SEL_ROW, |
| MTK_KPD_SEL_ROWMASK(keypad->n_rows)); |
| regmap_update_bits(keypad->regmap, MTK_KPD_SEL, MTK_KPD_SEL_COL, |
| MTK_KPD_SEL_COLMASK(keypad->n_cols)); |
| |
| keypad->clk = devm_clk_get_enabled(&pdev->dev, "kpd"); |
| if (IS_ERR(keypad->clk)) |
| return PTR_ERR(keypad->clk); |
| |
| irq = platform_get_irq(pdev, 0); |
| if (irq < 0) |
| return irq; |
| |
| error = devm_request_threaded_irq(&pdev->dev, irq, |
| NULL, mt6779_keypad_irq_handler, |
| IRQF_ONESHOT, MTK_KPD_NAME, keypad); |
| if (error) { |
| dev_err(&pdev->dev, "Failed to request IRQ#%d: %d\n", |
| irq, error); |
| return error; |
| } |
| |
| error = input_register_device(keypad->input_dev); |
| if (error) { |
| dev_err(&pdev->dev, "Failed to register device\n"); |
| return error; |
| } |
| |
| error = device_init_wakeup(&pdev->dev, wakeup); |
| if (error) |
| dev_warn(&pdev->dev, "device_init_wakeup() failed: %d\n", |
| error); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id mt6779_keypad_of_match[] = { |
| { .compatible = "mediatek,mt6779-keypad" }, |
| { .compatible = "mediatek,mt6873-keypad" }, |
| { /* sentinel */ } |
| }; |
| MODULE_DEVICE_TABLE(of, mt6779_keypad_of_match); |
| |
| static struct platform_driver mt6779_keypad_pdrv = { |
| .probe = mt6779_keypad_pdrv_probe, |
| .driver = { |
| .name = MTK_KPD_NAME, |
| .of_match_table = mt6779_keypad_of_match, |
| }, |
| }; |
| module_platform_driver(mt6779_keypad_pdrv); |
| |
| MODULE_AUTHOR("Mediatek Corporation"); |
| MODULE_DESCRIPTION("MTK Keypad (KPD) Driver"); |
| MODULE_LICENSE("GPL"); |