Some checks failed
CI / markdown-lint (push) Failing after 14s
- 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
378 lines
8.7 KiB
C
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");
|