cregit-Linux how code gets into the kernel

Release 4.11 drivers/input/rmi4/rmi_smbus.c

/*
 * Copyright (c) 2015 - 2016 Red Hat, Inc
 * Copyright (c) 2011, 2012 Synaptics Incorporated
 * Copyright (c) 2011 Unixphere
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published by
 * the Free Software Foundation.
 */

#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/kconfig.h>
#include <linux/lockdep.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/rmi.h>
#include <linux/slab.h>
#include "rmi_driver.h"


#define SMB_PROTOCOL_VERSION_ADDRESS	0xfd

#define SMB_MAX_COUNT			32

#define RMI_SMB2_MAP_SIZE		8 
/* 8 entry of 4 bytes each */

#define RMI_SMB2_MAP_FLAGS_WE		0x01


struct mapping_table_entry {
	
__le16 rmiaddr;
	
u8 readcount;
	
u8 flags;
};


struct rmi_smb_xport {
	
struct rmi_transport_dev xport;
	
struct i2c_client *client;

	
struct mutex page_mutex;
	
int page;
	
u8 table_index;
	
struct mutex mappingtable_mutex;
	
struct mapping_table_entry mapping_table[RMI_SMB2_MAP_SIZE];
};


static int rmi_smb_get_version(struct rmi_smb_xport *rmi_smb) { struct i2c_client *client = rmi_smb->client; int retval; /* Check if for SMBus new version device by reading version byte. */ retval = i2c_smbus_read_byte_data(client, SMB_PROTOCOL_VERSION_ADDRESS); if (retval < 0) { dev_err(&client->dev, "failed to get SMBus version number!\n"); return retval; } return retval + 1; }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires59100.00%1100.00%
Total59100.00%1100.00%

/* SMB block write - wrapper over ic2_smb_write_block */
static int smb_block_write(struct rmi_transport_dev *xport, u8 commandcode, const void *buf, size_t len) { struct rmi_smb_xport *rmi_smb = container_of(xport, struct rmi_smb_xport, xport); struct i2c_client *client = rmi_smb->client; int retval; retval = i2c_smbus_write_block_data(client, commandcode, len, buf); rmi_dbg(RMI_DEBUG_XPORT, &client->dev, "wrote %zd bytes at %#04x: %d (%*ph)\n", len, commandcode, retval, (int)len, buf); return retval; }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires90100.00%1100.00%
Total90100.00%1100.00%

/* * The function to get command code for smbus operations and keeps * records to the driver mapping table */
static int rmi_smb_get_command_code(struct rmi_transport_dev *xport, u16 rmiaddr, int bytecount, bool isread, u8 *commandcode) { struct rmi_smb_xport *rmi_smb = container_of(xport, struct rmi_smb_xport, xport); int i; int retval; struct mapping_table_entry mapping_data[1]; mutex_lock(&rmi_smb->mappingtable_mutex); for (i = 0; i < RMI_SMB2_MAP_SIZE; i++) { if (rmi_smb->mapping_table[i].rmiaddr == rmiaddr) { if (isread) { if (rmi_smb->mapping_table[i].readcount == bytecount) { *commandcode = i; retval = 0; goto exit; } } else { if (rmi_smb->mapping_table[i].flags & RMI_SMB2_MAP_FLAGS_WE) { *commandcode = i; retval = 0; goto exit; } } } } i = rmi_smb->table_index; rmi_smb->table_index = (i + 1) % RMI_SMB2_MAP_SIZE; /* constructs mapping table data entry. 4 bytes each entry */ memset(mapping_data, 0, sizeof(mapping_data)); mapping_data[0].rmiaddr = cpu_to_le16(rmiaddr); mapping_data[0].readcount = bytecount; mapping_data[0].flags = !isread ? RMI_SMB2_MAP_FLAGS_WE : 0; retval = smb_block_write(xport, i + 0x80, mapping_data, sizeof(mapping_data)); if (retval < 0) { /* * if not written to device mapping table * clear the driver mapping table records */ rmi_smb->mapping_table[i].rmiaddr = 0x0000; rmi_smb->mapping_table[i].readcount = 0; rmi_smb->mapping_table[i].flags = 0; goto exit; } /* save to the driver level mapping table */ rmi_smb->mapping_table[i].rmiaddr = rmiaddr; rmi_smb->mapping_table[i].readcount = bytecount; rmi_smb->mapping_table[i].flags = !isread ? RMI_SMB2_MAP_FLAGS_WE : 0; *commandcode = i; exit: mutex_unlock(&rmi_smb->mappingtable_mutex); return retval; }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires339100.00%1100.00%
Total339100.00%1100.00%


static int rmi_smb_write_block(struct rmi_transport_dev *xport, u16 rmiaddr, const void *databuff, size_t len) { int retval = 0; u8 commandcode; struct rmi_smb_xport *rmi_smb = container_of(xport, struct rmi_smb_xport, xport); int cur_len = (int)len; mutex_lock(&rmi_smb->page_mutex); while (cur_len > 0) { /* * break into 32 bytes chunks to write get command code */ int block_len = min_t(int, len, SMB_MAX_COUNT); retval = rmi_smb_get_command_code(xport, rmiaddr, block_len, false, &commandcode); if (retval < 0) goto exit; retval = smb_block_write(xport, commandcode, databuff, block_len); if (retval < 0) goto exit; /* prepare to write next block of bytes */ cur_len -= SMB_MAX_COUNT; databuff += SMB_MAX_COUNT; rmiaddr += SMB_MAX_COUNT; } exit: mutex_unlock(&rmi_smb->page_mutex); return retval; }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires155100.00%1100.00%
Total155100.00%1100.00%

/* SMB block read - wrapper over ic2_smb_read_block */
static int smb_block_read(struct rmi_transport_dev *xport, u8 commandcode, void *buf, size_t len) { struct rmi_smb_xport *rmi_smb = container_of(xport, struct rmi_smb_xport, xport); struct i2c_client *client = rmi_smb->client; int retval; retval = i2c_smbus_read_block_data(client, commandcode, buf); if (retval < 0) return retval; return retval; }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires71100.00%1100.00%
Total71100.00%1100.00%


static int rmi_smb_read_block(struct rmi_transport_dev *xport, u16 rmiaddr, void *databuff, size_t len) { struct rmi_smb_xport *rmi_smb = container_of(xport, struct rmi_smb_xport, xport); int retval; u8 commandcode; int cur_len = (int)len; mutex_lock(&rmi_smb->page_mutex); memset(databuff, 0, len); while (cur_len > 0) { /* break into 32 bytes chunks to write get command code */ int block_len = min_t(int, cur_len, SMB_MAX_COUNT); retval = rmi_smb_get_command_code(xport, rmiaddr, block_len, true, &commandcode); if (retval < 0) goto exit; retval = smb_block_read(xport, commandcode, databuff, block_len); if (retval < 0) goto exit; /* prepare to read next block of bytes */ cur_len -= SMB_MAX_COUNT; databuff += SMB_MAX_COUNT; rmiaddr += SMB_MAX_COUNT; } retval = 0; exit: mutex_unlock(&rmi_smb->page_mutex); return retval; }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires165100.00%1100.00%
Total165100.00%1100.00%


static void rmi_smb_clear_state(struct rmi_smb_xport *rmi_smb) { /* the mapping table has been flushed, discard the current one */ mutex_lock(&rmi_smb->mappingtable_mutex); memset(rmi_smb->mapping_table, 0, sizeof(rmi_smb->mapping_table)); mutex_unlock(&rmi_smb->mappingtable_mutex); }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires44100.00%1100.00%
Total44100.00%1100.00%


static int rmi_smb_enable_smbus_mode(struct rmi_smb_xport *rmi_smb) { int retval; /* we need to get the smbus version to activate the touchpad */ retval = rmi_smb_get_version(rmi_smb); if (retval < 0) return retval; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires34100.00%1100.00%
Total34100.00%1100.00%


static int rmi_smb_reset(struct rmi_transport_dev *xport, u16 reset_addr) { struct rmi_smb_xport *rmi_smb = container_of(xport, struct rmi_smb_xport, xport); rmi_smb_clear_state(rmi_smb); /* * we do not call the actual reset command, it has to be handled in * PS/2 or there will be races between PS/2 and SMBus. * PS/2 should ensure that a psmouse_reset is called before * intializing the device and after it has been removed to be in a known * state. */ return rmi_smb_enable_smbus_mode(rmi_smb); }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires41100.00%1100.00%
Total41100.00%1100.00%

static const struct rmi_transport_ops rmi_smb_ops = { .write_block = rmi_smb_write_block, .read_block = rmi_smb_read_block, .reset = rmi_smb_reset, };
static int rmi_smb_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct rmi_device_platform_data *pdata = dev_get_platdata(&client->dev); struct rmi_smb_xport *rmi_smb; int retval; int smbus_version; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_FUNC_SMBUS_HOST_NOTIFY)) { dev_err(&client->dev, "adapter does not support required functionality.\n"); return -ENODEV; } if (client->irq <= 0) { dev_err(&client->dev, "no IRQ provided, giving up.\n"); return client->irq ? client->irq : -ENODEV; } rmi_smb = devm_kzalloc(&client->dev, sizeof(struct rmi_smb_xport), GFP_KERNEL); if (!rmi_smb) return -ENOMEM; if (!pdata) { dev_err(&client->dev, "no platform data, aborting\n"); return -ENOMEM; } rmi_dbg(RMI_DEBUG_XPORT, &client->dev, "Probing %s.\n", dev_name(&client->dev)); rmi_smb->client = client; mutex_init(&rmi_smb->page_mutex); mutex_init(&rmi_smb->mappingtable_mutex); rmi_smb->xport.dev = &client->dev; rmi_smb->xport.pdata = *pdata; rmi_smb->xport.pdata.irq = client->irq; rmi_smb->xport.proto_name = "smb2"; rmi_smb->xport.ops = &rmi_smb_ops; retval = rmi_smb_get_version(rmi_smb); if (retval < 0) return retval; smbus_version = retval; rmi_dbg(RMI_DEBUG_XPORT, &client->dev, "Smbus version is %d", smbus_version); if (smbus_version != 2) { dev_err(&client->dev, "Unrecognized SMB version %d.\n", smbus_version); return -ENODEV; } i2c_set_clientdata(client, rmi_smb); retval = rmi_register_transport_device(&rmi_smb->xport); if (retval) { dev_err(&client->dev, "Failed to register transport driver at 0x%.2X.\n", client->addr); i2c_set_clientdata(client, NULL); return retval; } dev_info(&client->dev, "registered rmi smb driver at %#04x.\n", client->addr); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires364100.00%1100.00%
Total364100.00%1100.00%


static int rmi_smb_remove(struct i2c_client *client) { struct rmi_smb_xport *rmi_smb = i2c_get_clientdata(client); rmi_unregister_transport_device(&rmi_smb->xport); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires32100.00%1100.00%
Total32100.00%1100.00%


static int __maybe_unused rmi_smb_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct rmi_smb_xport *rmi_smb = i2c_get_clientdata(client); int ret; ret = rmi_driver_suspend(rmi_smb->xport.rmi_dev, true); if (ret) dev_warn(dev, "Failed to suspend device: %d\n", ret); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires64100.00%1100.00%
Total64100.00%1100.00%


static int __maybe_unused rmi_smb_runtime_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct rmi_smb_xport *rmi_smb = i2c_get_clientdata(client); int ret; ret = rmi_driver_suspend(rmi_smb->xport.rmi_dev, false); if (ret) dev_warn(dev, "Failed to suspend device: %d\n", ret); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires64100.00%1100.00%
Total64100.00%1100.00%


static int __maybe_unused rmi_smb_resume(struct device *dev) { struct i2c_client *client = container_of(dev, struct i2c_client, dev); struct rmi_smb_xport *rmi_smb = i2c_get_clientdata(client); struct rmi_device *rmi_dev = rmi_smb->xport.rmi_dev; int ret; rmi_smb_reset(&rmi_smb->xport, 0); rmi_reset(rmi_dev); ret = rmi_driver_resume(rmi_smb->xport.rmi_dev, true); if (ret) dev_warn(dev, "Failed to resume device: %d\n", ret); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires95100.00%1100.00%
Total95100.00%1100.00%


static int __maybe_unused rmi_smb_runtime_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct rmi_smb_xport *rmi_smb = i2c_get_clientdata(client); int ret; ret = rmi_driver_resume(rmi_smb->xport.rmi_dev, false); if (ret) dev_warn(dev, "Failed to resume device: %d\n", ret); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires64100.00%1100.00%
Total64100.00%1100.00%

static const struct dev_pm_ops rmi_smb_pm = { SET_SYSTEM_SLEEP_PM_OPS(rmi_smb_suspend, rmi_smb_resume) SET_RUNTIME_PM_OPS(rmi_smb_runtime_suspend, rmi_smb_runtime_resume, NULL) }; static const struct i2c_device_id rmi_id[] = { { "rmi4_smbus", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, rmi_id); static struct i2c_driver rmi_smb_driver = { .driver = { .name = "rmi4_smbus", .pm = &rmi_smb_pm, }, .id_table = rmi_id, .probe = rmi_smb_probe, .remove = rmi_smb_remove, }; module_i2c_driver(rmi_smb_driver); MODULE_AUTHOR("Andrew Duggan <aduggan@synaptics.com>"); MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@redhat.com>"); MODULE_DESCRIPTION("RMI4 SMBus driver"); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
Benjamin Tissoires1917100.00%1100.00%
Total1917100.00%1100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.