Files
BBeOS/drivers/input/q20-keyboard.c
Eliott 7b53cde2ae
Some checks failed
CI / markdown-lint (push) Failing after 14s
Complete BBeOS project implementation with BlackBerry-inspired website
- Updated .gitignore with comprehensive exclusions for build artifacts, IDE files, and OS-specific files
- Created BlackBerry-inspired website with Heroicons and Gitea integration
- Added complete project structure with all 7 phases implemented
- Included kernel drivers, UI components, telephony stack, and packaging tools
- Added emulation scripts for testing and development
- Comprehensive documentation for all development phases
- Security analysis and hardware testing guides
- SDK and application framework for third-party development
2025-08-01 10:20:28 +02:00

378 lines
8.7 KiB
C

/*
* Q20 Keyboard Driver
* BlackBerry Classic Q20 Physical QWERTY Keyboard
*
* This driver provides support for the Q20's physical keyboard
* connected via I2C to the MSM8960 SoC.
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/regulator/consumer.h>
#define Q20_KB_DEVICE_NAME "q20-keyboard"
#define Q20_KB_DRIVER_NAME "q20-keyboard"
/* Keyboard registers */
#define Q20_KB_REG_STATUS 0x00
#define Q20_KB_REG_DATA 0x01
#define Q20_KB_REG_CONFIG 0x02
#define Q20_KB_REG_INT_EN 0x03
/* Status register bits */
#define Q20_KB_STATUS_DATA_READY BIT(0)
#define Q20_KB_STATUS_ERROR BIT(1)
/* Configuration register bits */
#define Q20_KB_CONFIG_ENABLE BIT(0)
#define Q20_KB_CONFIG_INT_ENABLE BIT(1)
/* Interrupt enable register bits */
#define Q20_KB_INT_DATA_READY BIT(0)
#define Q20_KB_INT_ERROR BIT(1)
/* Key codes for Q20 keyboard */
#define Q20_KB_KEY_COUNT 64
struct q20_keyboard {
struct i2c_client *client;
struct input_dev *input;
struct gpio_desc *irq_gpio;
struct gpio_desc *reset_gpio;
struct regulator *supply;
struct work_struct work;
struct mutex lock;
bool enabled;
u8 keymap[Q20_KB_KEY_COUNT];
};
static const unsigned short q20_keymap[] = {
/* Row 0: Function keys */
KEY_ESC, KEY_F1, KEY_F2, KEY_F3, KEY_F4,
KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9,
KEY_F10, KEY_F11, KEY_F12, KEY_PRTSC, KEY_SCROLLLOCK,
KEY_PAUSE,
/* Row 1: Number row */
KEY_GRAVE, KEY_1, KEY_2, KEY_3, KEY_4,
KEY_5, KEY_6, KEY_7, KEY_8, KEY_9,
KEY_0, KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE,
/* Row 2: QWERTY row */
KEY_TAB, KEY_Q, KEY_W, KEY_E, KEY_R,
KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O,
KEY_P, KEY_LEFTBRACE, KEY_RIGHTBRACE, KEY_BACKSLASH,
/* Row 3: ASDF row */
KEY_CAPSLOCK, KEY_A, KEY_S, KEY_D, KEY_F,
KEY_G, KEY_H, KEY_J, KEY_K, KEY_L,
KEY_SEMICOLON, KEY_APOSTROPHE, KEY_ENTER,
/* Row 4: ZXCV row */
KEY_LEFTSHIFT, KEY_Z, KEY_X, KEY_C, KEY_V,
KEY_B, KEY_N, KEY_M, KEY_COMMA, KEY_DOT,
KEY_SLASH, KEY_RIGHTSHIFT,
/* Row 5: Control row */
KEY_LEFTCTRL, KEY_LEFTMETA, KEY_LEFTALT, KEY_SPACE,
KEY_RIGHTALT, KEY_RIGHTMETA, KEY_RIGHTCTRL, KEY_LEFT,
KEY_UP, KEY_DOWN, KEY_RIGHT,
/* Special keys */
KEY_MENU, KEY_HOME, KEY_END, KEY_PAGEUP,
KEY_PAGEDOWN, KEY_INSERT, KEY_DELETE,
};
static int q20_kb_read_reg(struct q20_keyboard *kb, u8 reg, u8 *val)
{
int ret;
ret = i2c_smbus_read_byte_data(kb->client, reg);
if (ret < 0) {
dev_err(&kb->client->dev, "Failed to read reg 0x%02x: %d\n", reg, ret);
return ret;
}
*val = ret;
return 0;
}
static int q20_kb_write_reg(struct q20_keyboard *kb, u8 reg, u8 val)
{
int ret;
ret = i2c_smbus_write_byte_data(kb->client, reg, val);
if (ret < 0) {
dev_err(&kb->client->dev, "Failed to write reg 0x%02x: %d\n", reg, ret);
return ret;
}
return 0;
}
static void q20_kb_work_handler(struct work_struct *work)
{
struct q20_keyboard *kb = container_of(work, struct q20_keyboard, work);
u8 status, data;
int i, ret;
mutex_lock(&kb->lock);
/* Read status register */
ret = q20_kb_read_reg(kb, Q20_KB_REG_STATUS, &status);
if (ret < 0) {
dev_err(&kb->client->dev, "Failed to read status\n");
goto unlock;
}
if (!(status & Q20_KB_STATUS_DATA_READY)) {
dev_dbg(&kb->client->dev, "No data ready\n");
goto unlock;
}
/* Read key data */
ret = q20_kb_read_reg(kb, Q20_KB_REG_DATA, &data);
if (ret < 0) {
dev_err(&kb->client->dev, "Failed to read key data\n");
goto unlock;
}
/* Process key states */
for (i = 0; i < Q20_KB_KEY_COUNT; i++) {
bool pressed = data & (1 << (i % 8));
bool was_pressed = kb->keymap[i / 8] & (1 << (i % 8));
if (pressed != was_pressed) {
dev_dbg(&kb->client->dev, "Key %d %s\n", i, pressed ? "pressed" : "released");
if (i < ARRAY_SIZE(q20_keymap)) {
input_report_key(kb->input, q20_keymap[i], pressed);
}
}
}
/* Update keymap */
for (i = 0; i < Q20_KB_KEY_COUNT / 8; i++) {
ret = q20_kb_read_reg(kb, Q20_KB_REG_DATA + i, &kb->keymap[i]);
if (ret < 0) {
dev_err(&kb->client->dev, "Failed to read keymap[%d]\n", i);
break;
}
}
input_sync(kb->input);
unlock:
mutex_unlock(&kb->lock);
}
static irqreturn_t q20_kb_irq_handler(int irq, void *dev_id)
{
struct q20_keyboard *kb = dev_id;
/* Schedule work to handle the interrupt */
schedule_work(&kb->work);
return IRQ_HANDLED;
}
static int q20_kb_enable(struct q20_keyboard *kb)
{
int ret;
if (kb->enabled)
return 0;
dev_info(&kb->client->dev, "Enabling Q20 keyboard\n");
/* Enable power supply */
if (kb->supply) {
ret = regulator_enable(kb->supply);
if (ret < 0) {
dev_err(&kb->client->dev, "Failed to enable supply: %d\n", ret);
return ret;
}
}
/* Reset keyboard */
if (kb->reset_gpio) {
gpiod_set_value_cansleep(kb->reset_gpio, 0);
msleep(10);
gpiod_set_value_cansleep(kb->reset_gpio, 1);
msleep(50);
}
/* Configure keyboard */
ret = q20_kb_write_reg(kb, Q20_KB_REG_CONFIG, Q20_KB_CONFIG_ENABLE);
if (ret < 0) {
dev_err(&kb->client->dev, "Failed to enable keyboard\n");
return ret;
}
/* Enable interrupts */
ret = q20_kb_write_reg(kb, Q20_KB_REG_INT_EN, Q20_KB_INT_DATA_READY);
if (ret < 0) {
dev_err(&kb->client->dev, "Failed to enable interrupts\n");
return ret;
}
kb->enabled = true;
return 0;
}
static void q20_kb_disable(struct q20_keyboard *kb)
{
if (!kb->enabled)
return;
dev_info(&kb->client->dev, "Disabling Q20 keyboard\n");
/* Disable interrupts */
q20_kb_write_reg(kb, Q20_KB_REG_INT_EN, 0);
/* Disable keyboard */
q20_kb_write_reg(kb, Q20_KB_REG_CONFIG, 0);
/* Disable power supply */
if (kb->supply)
regulator_disable(kb->supply);
kb->enabled = false;
}
static int q20_kb_probe(struct i2c_client *client)
{
struct q20_keyboard *kb;
struct device *dev = &client->dev;
int ret, irq;
dev_info(dev, "Probing Q20 keyboard\n");
kb = devm_kzalloc(dev, sizeof(*kb), GFP_KERNEL);
if (!kb)
return -ENOMEM;
kb->client = client;
i2c_set_clientdata(client, kb);
mutex_init(&kb->lock);
INIT_WORK(&kb->work, q20_kb_work_handler);
/* Get power supply */
kb->supply = devm_regulator_get(dev, "vdd");
if (IS_ERR(kb->supply)) {
ret = PTR_ERR(kb->supply);
if (ret != -EPROBE_DEFER)
dev_err(dev, "Failed to get vdd regulator: %d\n", ret);
return ret;
}
/* Get reset GPIO */
kb->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(kb->reset_gpio)) {
ret = PTR_ERR(kb->reset_gpio);
dev_err(dev, "Failed to get reset GPIO: %d\n", ret);
return ret;
}
/* Get interrupt GPIO */
kb->irq_gpio = devm_gpiod_get(dev, "irq", GPIOD_IN);
if (IS_ERR(kb->irq_gpio)) {
ret = PTR_ERR(kb->irq_gpio);
dev_err(dev, "Failed to get IRQ GPIO: %d\n", ret);
return ret;
}
/* Get IRQ number */
irq = gpiod_to_irq(kb->irq_gpio);
if (irq < 0) {
dev_err(dev, "Failed to get IRQ number: %d\n", irq);
return irq;
}
/* Create input device */
kb->input = devm_input_allocate_device(dev);
if (!kb->input) {
dev_err(dev, "Failed to allocate input device\n");
return -ENOMEM;
}
kb->input->name = "BlackBerry Q20 Keyboard";
kb->input->phys = "q20-keyboard/input0";
kb->input->id.bustype = BUS_I2C;
kb->input->id.vendor = 0x0001;
kb->input->id.product = 0x0001;
kb->input->id.version = 0x0100;
/* Set keycodes */
input_set_capability(kb->input, EV_KEY, KEY_ESC);
for (int i = 0; i < ARRAY_SIZE(q20_keymap); i++) {
set_bit(q20_keymap[i], kb->input->keybit);
}
/* Register input device */
ret = input_register_device(kb->input);
if (ret < 0) {
dev_err(dev, "Failed to register input device: %d\n", ret);
return ret;
}
/* Request IRQ */
ret = devm_request_irq(dev, irq, q20_kb_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
Q20_KB_DRIVER_NAME, kb);
if (ret < 0) {
dev_err(dev, "Failed to request IRQ: %d\n", ret);
return ret;
}
/* Enable keyboard */
ret = q20_kb_enable(kb);
if (ret < 0) {
dev_err(dev, "Failed to enable keyboard: %d\n", ret);
return ret;
}
dev_info(dev, "Q20 keyboard probed successfully\n");
return 0;
}
static int q20_kb_remove(struct i2c_client *client)
{
struct q20_keyboard *kb = i2c_get_clientdata(client);
dev_info(&client->dev, "Removing Q20 keyboard\n");
q20_kb_disable(kb);
cancel_work_sync(&kb->work);
return 0;
}
static const struct of_device_id q20_kb_of_match[] = {
{ .compatible = "blackberry,q20-keyboard" },
{ }
};
MODULE_DEVICE_TABLE(of, q20_kb_of_match);
static struct i2c_driver q20_kb_driver = {
.driver = {
.name = Q20_KB_DRIVER_NAME,
.of_match_table = q20_kb_of_match,
},
.probe = q20_kb_probe,
.remove = q20_kb_remove,
};
module_i2c_driver(q20_kb_driver);
MODULE_AUTHOR("BBeOS Team");
MODULE_DESCRIPTION("BlackBerry Classic Q20 Keyboard Driver");
MODULE_LICENSE("GPL v2");